Ottimizzare l'accesso ai file

Crea app UWP in grado di accedere al file system in modo efficiente, evitando problemi di prestazioni dovuti alla latenza del disco e a cicli di memoria/CPU.

Consigliamo di eseguire l’accesso a una raccolta di file di grandi dimensioni e a valori di proprietà diversi creando QueryOptions e chiamando SetPropertyPrefetch anziché chiamando le tipiche proprietà Name, FileType e Path. Il metodo SetPropertyPrefetch può migliorare radicalmente le prestazioni delle app che visualizzano una raccolta di elementi ottenuti dal file system, come ad esempio una raccolta di immagini. Nella successiva serie di esempi vengono illustrati alcuni modi per accedere a più file.

Nel primo esempio si utilizza Windows.Storage.StorageFolder.GetFilesAsync per recuperare le informazioni sul nome per una serie di file. Questo approccio offre ottime prestazioni, perché nell'esempio si accede esclusivamente alla proprietà relativa al nome.

StorageFolder library = Windows.Storage.KnownFolders.PicturesLibrary;
IReadOnlyList<StorageFile> files = await library.GetFilesAsync(Windows.Storage.Search.CommonFileQuery.OrderByDate);

for (int i = 0; i < files.Count; i++)
{
    // do something with the name of each file
    string fileName = files[i].Name;
}
Dim library As StorageFolder = Windows.Storage.KnownFolders.PicturesLibrary
Dim files As IReadOnlyList(Of StorageFile) =
    Await library.GetFilesAsync(Windows.Storage.Search.CommonFileQuery.OrderByDate)

For i As Integer = 0 To files.Count - 1
    ' do something with the name of each file
    Dim fileName As String = files(i).Name
Next i

Nel secondo esempio si utilizza Windows.Storage.StorageFolder.GetFilesAsync e quindi si recuperano le proprietà dell'immagine per ciascun file. Questo approccio offre prestazioni poco efficienti.

StorageFolder library = Windows.Storage.KnownFolders.PicturesLibrary;
IReadOnlyList<StorageFile> files = await library.GetFilesAsync(Windows.Storage.Search.CommonFileQuery.OrderByDate);
for (int i = 0; i < files.Count; i++)
{
    ImageProperties imgProps = await files[i].Properties.GetImagePropertiesAsync();

    // do something with the date the image was taken
    DateTimeOffset date = imgProps.DateTaken;
}
Dim library As StorageFolder = Windows.Storage.KnownFolders.PicturesLibrary
Dim files As IReadOnlyList(Of StorageFile) = Await library.GetFilesAsync(Windows.Storage.Search.CommonFileQuery.OrderByDate)
For i As Integer = 0 To files.Count - 1
    Dim imgProps As ImageProperties =
        Await files(i).Properties.GetImagePropertiesAsync()

    ' do something with the date the image was taken
    Dim dateTaken As DateTimeOffset = imgProps.DateTaken
Next i

Nel terzo esempio si utilizza QueryOptions per ottenere informazioni su una serie di file. Le prestazioni garantite da questo approccio sono molto migliori rispetto a quelle dell'esempio precedente.

// Set QueryOptions to prefetch our specific properties
var queryOptions = new Windows.Storage.Search.QueryOptions(CommonFileQuery.OrderByDate, null);
queryOptions.SetThumbnailPrefetch(ThumbnailMode.PicturesView, 100,
        ThumbnailOptions.ReturnOnlyIfCached);
queryOptions.SetPropertyPrefetch(PropertyPrefetchOptions.ImageProperties, 
       new string[] {"System.Size"});

StorageFileQueryResult queryResults = KnownFolders.PicturesLibrary.CreateFileQueryWithOptions(queryOptions);
IReadOnlyList<StorageFile> files = await queryResults.GetFilesAsync();

foreach (var file in files)
{
    ImageProperties imageProperties = await file.Properties.GetImagePropertiesAsync();

    // Do something with the date the image was taken.
    DateTimeOffset dateTaken = imageProperties.DateTaken;

    // Performance gains increase with the number of properties that are accessed.
    IDictionary<String, object> propertyResults =
        await file.Properties.RetrievePropertiesAsync(
              new string[] {"System.Size" });

    // Get/Set extra properties here
    var systemSize = propertyResults["System.Size"];
}
' Set QueryOptions to prefetch our specific properties
Dim queryOptions = New Windows.Storage.Search.QueryOptions(CommonFileQuery.OrderByDate, Nothing)
queryOptions.SetThumbnailPrefetch(ThumbnailMode.PicturesView,
            100, Windows.Storage.FileProperties.ThumbnailOptions.ReturnOnlyIfCached)
queryOptions.SetPropertyPrefetch(PropertyPrefetchOptions.ImageProperties,
                                 New String() {"System.Size"})

Dim queryResults As StorageFileQueryResult = KnownFolders.PicturesLibrary.CreateFileQueryWithOptions(queryOptions)
Dim files As IReadOnlyList(Of StorageFile) = Await queryResults.GetFilesAsync()


For Each file In files
    Dim imageProperties As ImageProperties = Await file.Properties.GetImagePropertiesAsync()

    ' Do something with the date the image was taken.
    Dim dateTaken As DateTimeOffset = imageProperties.DateTaken

    ' Performance gains increase with the number of properties that are accessed.
    Dim propertyResults As IDictionary(Of String, Object) =
        Await file.Properties.RetrievePropertiesAsync(New String() {"System.Size"})

    ' Get/Set extra properties here
    Dim systemSize = propertyResults("System.Size")

Next file

Se vengono eseguite più operazioni su oggetti Windows.Storage come ad esempio Windows.Storage.ApplicationData.Current.LocalFolder, è possibile creare una variabile locale di rifermento a tale origine di archiviazione in modo da non ricreare oggetti intermedi a ogni accesso.

Prestazioni dei flussi in C# e Visual Basic

Buffering tra flussi UWP e .NET

In molti scenari potresti voler convertire un flusso UWP, come Windows.Storage.Streams.IInputStream o IOutputStream, in un flusso .NET (System.IO.Stream). Questa conversione è utile, ad esempio, se scrivi un'app UWP e vuoi usare codice .NET esistente compatibile con i flussi del file system UWP. A questo scopo, le API .NET per le app UWP forniscono metodi di estensione che ti permettono di eseguire la conversione tra tipi di flusso .NET e UWP. Per altre info, vedi WindowsRuntimeStreamExtensions.

Quando converti un flusso UWP in un flusso .NET, in realtà crei un adattatore per il flusso UWP sottostante. In alcuno casi, il richiamo di metodi su flussi UWP è associato a un impatto per il runtime. Questo può influire sulla velocità dell'app, in particolare negli scenari in cui esegui molte piccole operazioni di lettura o scrittura frequenti.

Per accelerare le app, gli adattatori del flusso UWP contengono un buffer dei dati. L'esempio di codice seguente mostra piccole operazioni di lettura consecutive usando un adattatore del flusso UWP con una dimensione predefinita del buffer.

StorageFile file = await Windows.Storage.ApplicationData.Current
    .LocalFolder.GetFileAsync("example.txt");
Windows.Storage.Streams.IInputStream windowsRuntimeStream = 
    await file.OpenReadAsync();

byte[] destinationArray = new byte[8];

// Create an adapter with the default buffer size.
using (var managedStream = windowsRuntimeStream.AsStreamForRead())
{

    // Read 8 bytes into destinationArray.
    // A larger block is actually read from the underlying 
    // windowsRuntimeStream and buffered within the adapter.
    await managedStream.ReadAsync(destinationArray, 0, 8);

    // Read 8 more bytes into destinationArray.
    // This call may complete much faster than the first call
    // because the data is buffered and no call to the 
    // underlying windowsRuntimeStream needs to be made.
    await managedStream.ReadAsync(destinationArray, 0, 8);
}
Dim file As StorageFile = Await Windows.Storage.ApplicationData.Current -
.LocalFolder.GetFileAsync("example.txt")
Dim windowsRuntimeStream As Windows.Storage.Streams.IInputStream =
    Await file.OpenReadAsync()

Dim destinationArray() As Byte = New Byte(8) {}

' Create an adapter with the default buffer size.
Dim managedStream As Stream = windowsRuntimeStream.AsStreamForRead()
Using (managedStream)

    ' Read 8 bytes into destinationArray.
    ' A larger block is actually read from the underlying 
    ' windowsRuntimeStream and buffered within the adapter.
    Await managedStream.ReadAsync(destinationArray, 0, 8)

    ' Read 8 more bytes into destinationArray.
    ' This call may complete much faster than the first call
    ' because the data is buffered and no call to the 
    ' underlying windowsRuntimeStream needs to be made.
    Await managedStream.ReadAsync(destinationArray, 0, 8)

End Using

Questo comportamento di buffering predefinito è quello più adatto nella maggior parte degli scenari in cui devi convertire un flusso UWP in un flusso .NET. Tuttavia, in alcuni scenari potresti voler modificare il comportamento di buffering per migliorare le prestazioni.

Uso di set di dati di grandi dimensioni

Per la lettura o la scrittura di grandi set di dati puoi avere la possibilità di aumentare la velocità effettiva di lettura o scrittura fornendo un buffer di grandi dimensioni ai metodi di estensione AsStreamForRead, AsStreamForWrite e AsStream. In questo modo l'adattatore del flusso ha a disposizione un buffer interno di dimensioni maggiori. Durante il passaggio di un flusso proveniente da un file di grandi dimensioni a un parser XML, ad esempio, il parser può eseguire molte piccole operazioni di lettura in sequenza dal flusso. Un buffer di grandi dimensioni può ridurre il numero di chiamate al flusso UWP sottostante e incrementare le prestazioni.

Nota Presta attenzione quando imposti dimensioni del buffer maggiori di 80 KB circa, perché si potrebbe verificare una frammentazione nell'heap di Garbage Collector (vedi Migliorare le prestazioni di Garbage Collection). L'esempio di codice seguente crea un adattatore di flusso gestito con un buffer da 81.920 byte.

// Create a stream adapter with an 80 KB buffer.
Stream managedStream = nativeStream.AsStreamForRead(bufferSize: 81920);
' Create a stream adapter with an 80 KB buffer.
Dim managedStream As Stream = nativeStream.AsStreamForRead(bufferSize:=81920)

I metodi Stream.CopyTo e CopyToAsync allocano inoltre un buffer locale per la copia tra i flussi. Come per il metodo di estensione AsStreamForRead, puoi ottenere prestazioni più elevate per grandi operazioni di copia dei flussi ignorando le dimensioni predefinite del buffer. L'esempio di codice seguente mostra la modifica delle dimensioni predefinite del buffer di una chiamata a CopyToAsync.

MemoryStream destination = new MemoryStream();
// copies the buffer into memory using the default copy buffer
await managedStream.CopyToAsync(destination);

// Copy the buffer into memory using a 1 MB copy buffer.
await managedStream.CopyToAsync(destination, bufferSize: 1024 * 1024);
Dim destination As MemoryStream = New MemoryStream()
' copies the buffer into memory using the default copy buffer
Await managedStream.CopyToAsync(destination)

' Copy the buffer into memory using a 1 MB copy buffer.
Await managedStream.CopyToAsync(destination, bufferSize:=1024 * 1024)

L'esempio usa un buffer di dimensioni pari a 1 MB, ossia maggiori degli 80 kB consigliati in precedenza. L'uso di un buffer così grande può migliorare la velocità effettiva dell'operazione di copia per set di dati molto grandi, ovvero da diverse centinaia di megabyte. Tuttavia, questo buffer è allocato sull'heap di oggetti di grandi dimensioni e potrebbe potenzialmente degradare le prestazioni di Garbage Collection. Usa buffer di grandi dimensioni solo se ti consentiranno di migliorare sensibilmente le prestazioni della tua app.

Quando usi un numero elevato di flussi simultaneamente, puoi decidere di ridurre o eliminare l'overhead della memoria del buffer. Puoi specificare un buffer di dimensioni minori o impostare il parametro bufferSize su 0 per disattivare completamente il buffering per uno specifico adattatore di flusso. Puoi comunque ottenere una velocità effettiva soddisfacente senza buffering se esegui grandi operazioni di lettura e scrittura nel flusso gestito.

Esecuzione di operazioni sensibili alla latenza

Puoi scegliere di evitare il buffering anche se vuoi impostare operazioni di lettura e scrittura a bassa latenza senza leggere grandi blocchi dal flusso UWP sottostante. Ad esempio, potresti voler impostare operazioni di lettura e scrittura a bassa latenza se usi il flusso per le comunicazioni di rete.

In un'app di chat è possibile usare un flusso su un'interfaccia di rete per inviare messaggi avanti e indietro. In questo caso, vorrai inviare i messaggi non appena sono pronti, senza aspettare il riempimento del buffer. Se imposti le dimensioni del buffer su 0 quando chiami i metodi di estensione AsStreamForRead, AsStreamForWrite e AsStream, l’adattatore risultante non allocherà un buffer e tutte le chiamate modificheranno direttamente il flusso UWP sottostante.