Udostępnij przez


Optymalizowanie dostępu do plików

Tworzenie aplikacji platformy uniwersalnej systemu Windows (UWP), które wydajnie uzyskują dostęp do systemu plików, co pozwala uniknąć problemów z wydajnością spowodowanych opóźnieniami dysku i cyklami pamięci/procesora CPU.

Jeśli chcesz uzyskać dostęp do dużej kolekcji plików i chcesz uzyskać dostęp do wartości właściwości innych niż typowe właściwości Name, FileType i Path, uzyskaj do nich dostęp, tworząc QueryOptions i wywołując SetPropertyPrefetch. Metoda SetPropertyPrefetch może znacznie poprawić wydajność aplikacji, które wyświetlają kolekcję elementów uzyskanych z systemu plików, takich jak kolekcja obrazów. W następnym zestawie przykładów przedstawiono kilka sposobów uzyskiwania dostępu do wielu plików.

W pierwszym przykładzie użyto Windows.Storage.StorageFolder.GetFilesAsync w celu pobrania informacji o nazwie dla zestawu plików. Takie podejście zapewnia dobrą wydajność, ponieważ przykład uzyskuje dostęp tylko do właściwości nazwy.

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

W drugim przykładzie użyto Windows.Storage.StorageFolder.GetFilesAsync, a następnie pobiera właściwości obrazu dla każdego pliku. Takie podejście zapewnia niską wydajność.

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

W trzecim przykładzie użyto QueryOptions w celu uzyskania informacji o zestawie plików. Takie podejście zapewnia znacznie lepszą wydajność niż w poprzednim przykładzie.

// 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

Jeśli wykonujesz wiele operacji na obiektach Windows.Storage, takich jak Windows.Storage.ApplicationData.Current.LocalFolder, utwórz zmienną lokalną, aby odwoływać się do tego źródła magazynu, aby nie tworzyć ponownie obiektów pośrednich za każdym razem, gdy uzyskujesz do niego dostęp.

Wydajność strumienia w językach C# i Visual Basic

Buforowanie między strumieniami platformy UWP i platformy .NET

Istnieje wiele scenariuszy, w których można przekonwertować strumień platformy UWP (na przykład Windows.Storage.Streams.IInputStream lub IOutputStream) do strumienia platformy .NET (System.IO.Stream). Na przykład jest to przydatne podczas pisania aplikacji platformy UWP i chcesz użyć istniejącego kodu platformy .NET, który działa na strumieniach w systemie plików platformy UWP. Aby to umożliwić, interfejsy API platformy .NET dla aplikacji platformy UWP udostępniają metody rozszerzeń, które umożliwiają konwertowanie między typami strumieni platformy .NET i platformy UWP. Aby uzyskać więcej informacji, zobacz WindowsRuntimeStreamExtensions.

Podczas konwertowania strumienia UWP na strumień .NET, skutecznie tworzysz adapter dla bazowego strumienia UWP. W niektórych okolicznościach istnieje koszt środowiska uruchomieniowego związany z wywoływaniem metod w strumieniach platformy UWP. Może to mieć wpływ na szybkość aplikacji, szczególnie w scenariuszach, w których wykonujesz wiele małych, częstych operacji odczytu lub zapisu.

Aby przyspieszyć działanie aplikacji, adaptery strumieniowe Uniwersalnej platformy Windows zawierają bufor danych. Poniższy przykład kodu przedstawia małe, ciągłe odczyty przy użyciu adaptera strumienia UWP z domyślnym rozmiarem buforu.

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

To domyślne zachowanie buforowania jest pożądane w większości scenariuszy, w których strumień platformy UWP jest konwertowany na strumień platformy .NET. Jednak w niektórych scenariuszach można dostosować zachowanie buforowania w celu zwiększenia wydajności.

Praca z dużymi zestawami danych

Podczas odczytywania lub zapisywania większych zestawów danych możesz zwiększyć przepustowość odczytu lub zapisu, udostępniając duży bufor metodom rozszerzającym AsStreamForRead, AsStreamForWritei AsStream. Zapewnia to adapterowi strumienia większy rozmiar buforu wewnętrznego. Na przykład podczas przekazywania strumienia, który pochodzi z dużego pliku do analizatora XML, analizator może wykonywać wiele małych sekwencyjnych odczytów ze strumienia. Duży bufor może zmniejszyć liczbę wywołań do podstawowego strumienia UWP i zwiększyć wydajność.

Uwaga Należy zachować ostrożność podczas ustawiania rozmiaru buforu większego niż około 80 KB, ponieważ może to spowodować fragmentację sterty zbieracza śmieci (zobacz Poprawianie wydajności odzyskiwania pamięci). Poniższy przykład kodu tworzy adaptator zarządzanego strumienia z buforem 81 920 bajtów.

// 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)

Metody Stream.CopyTo i CopyToAsync przydzielają również lokalny bufor do kopiowania między strumieniami. Podobnie jak w przypadku metody rozszerzenia AsStreamForRead, może być możliwe uzyskanie lepszej wydajności dla dużych kopii strumieniowych przez zastąpienie domyślnego rozmiaru buforu. Poniższy przykład kodu pokazuje zmianę domyślnego rozmiaru buforu wywołania 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)

W tym przykładzie użyto rozmiaru buforu o rozmiarze 1 MB, który jest większy niż zalecane wcześniej 80 KB. Użycie takiego dużego buforu może zwiększyć przepływność operacji kopiowania w przypadku bardzo dużych zestawów danych (czyli kilkuset megabajtów). Jednak ten bufor jest przydzielany na stercie dużych obiektów i może potencjalnie obniżyć wydajność odzyskiwania pamięci. Należy używać tylko dużych rozmiarów buforów, jeśli to znacząco poprawi wydajność aplikacji.

Podczas pracy z dużą liczbą strumieni jednocześnie możesz zmniejszyć lub wyeliminować obciążenie pamięci buforu. Można ustawić mniejszy bufor lub ustawić parametr bufferSize na 0, aby całkowicie wyłączyć buforowanie dla tego adaptera strumienia. Nadal można osiągnąć dobrą wydajność przepływności bez buforowania, jeśli wykonujesz duże operacje odczytu i zapisu w zarządzanym strumieniu.

Wykonywanie operacji wrażliwych na opóźnienia

Warto również unikać buforowania, jeśli chcesz operacje odczytu i zapisu o niskim opóźnieniu i nie chcesz odczytywać dużych bloków z bazowego strumienia UWP. Na przykład możesz chcieć odczytów z niskim opóźnieniem i zapisów, jeśli używasz strumienia do komunikacji sieciowej.

W aplikacji do czatu możesz użyć strumienia za pośrednictwem interfejsu sieciowego do wysyłania komunikatów tam i z powrotem. W takim przypadku chcesz wysyłać komunikaty tak szybko, jak są gotowe i nie czekać na wypełnienie buforu. Jeśli podczas wywoływania metod rozszerzenia AsStreamForRead, AsStreamForWritei AsStream ustawisz rozmiar bufora na 0, wynikowy adapter nie przydzieli buforu, a wszystkie wywołania będą manipulować bezpośrednio bazowym strumieniem platformy UWP.