Optymalizowanie dostępu do plików

Tworzenie aplikacji WinUI za pomocą zestawu SDK aplikacji systemu Windows, które wydajnie uzyskują dostęp do systemu plików, unikając problemów z wydajnością spowodowanych opóźnieniem dysku i niepotrzebnym użyciem pamięci lub 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 element QueryOptions i wywołując polecenie 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 pliku Windows.StorageFolder.GetFilesAsync , aby pobrać informacje o nazwie dla zestawu plików. Takie podejście zapewnia dobrą wydajność, ponieważ przykład uzyskuje dostęp tylko do właściwości 'nazwa'.

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;
}

W drugim przykładzie użyto elementu Windows.StorageFolder.GetFilesAsync , a następnie pobrano 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;
}

W trzecim przykładzie użyto funkcji QueryOptions , aby uzyskać informacje 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 or set extra properties here.
    var systemSize = propertyResults["System.Size"];
}

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ęzyku C#

Buforowanie między strumieniami środowiska uruchomieniowego systemu Windows i platformy .NET

Istnieje wiele scenariuszy, w których można przekonwertować strumień środowiska uruchomieniowego systemu Windows (na przykład Windows.Storage.Streams.IInputStream lub IOutputStream) na strumień platformy .NET (System.IO.Stream). Na przykład, kiedy piszesz aplikację WinUI i chcesz użyć istniejącego kodu .NET, który działa ze strumieniami w połączeniu z interfejsami API Windows Storage, jest to przydatne. Aby to włączyć, platforma .NET udostępnia metody rozszerzeń, które umożliwiają konwertowanie między typami strumieni środowiska uruchomieniowego systemu Windows i platformy .NET. Aby uzyskać więcej informacji, zobacz WindowsRuntimeStreamExtensions.

Podczas konwertowania strumienia środowiska uruchomieniowego systemu Windows na strumień platformy .NET, będziesz skutecznie tworzyć adapter dla bazowego strumienia środowiska uruchomieniowego systemu Windows. W niektórych okolicznościach występuje koszt środowiska uruchomieniowego związany z wywoływaniem metod w strumieniach środowiska uruchomieniowego systemu Windows. 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 strumieni środowiska uruchomieniowego Windows zawierają bufor danych. Poniższy przykładowy kod przedstawia małe kolejne operacje odczytu przy użyciu adaptera strumienia Windows Runtime 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);
}

To domyślne zachowanie buforowania jest pożądane w większości scenariuszy, w których strumień środowiska uruchomieniowego systemu Windows 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że być możliwe zwiększenie przepływności odczytu lub zapisu, zapewniając duży rozmiar buforu do metod rozszerzenia AsStreamForRead, AsStreamForWrite i 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 wykonać wiele małych sekwencyjnych odczytów ze strumienia. Duży bufor może zmniejszyć liczbę wywołań do bazowego strumienia środowiska uruchomieniowego systemu Windows i zwiększyć wydajność.

Uwaga / Notatka

Należy zachować ostrożność podczas ustawiania rozmiaru buforu większego niż około 80 KB, ponieważ może to spowodować fragmentację sterty zarządzanej przez garbage collector. Aby uzyskać powiązane wskazówki, zobacz Zwiększanie wydajności odzyskiwania pamięci w aplikacjach WinUI. Poniższy przykład kodu tworzy zarządzany adapter strumienia z buforem 81 920 bajtów.

// Create a stream adapter with an 80 KB buffer.
Stream managedStream = 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żesz uzyskać lepszą wydajność dla dużych kopii strumieni, przesłaniając domyślny rozmiar buforu. W poniższym przykładzie kodu pokazano 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);

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ć dużych rozmiarów buforów tylko wtedy, gdy znacznie poprawią wydajność aplikacji.

Podczas pracy z dużą liczbą strumieni jednocześnie możesz zmniejszyć lub wyeliminować obciążenie pamięci buforu. Można określić mniejszy bufor lub ustawić parametr bufferSize na wartość 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

Możesz również uniknąć buforowania, jeśli chcesz realizować odczyty i zapisy z niskimi opóźnieniami i nie chcesz odczytywać w dużych blokach z podstawowego strumienia Windows Runtime. Na przykład możesz chcieć uzyskać niskie opóźnienia podczas odczytu i zapisu, 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 tylko są gotowe i nie czekać na wypełnienie buforu. W przypadku ustawienia rozmiaru buforu na 0 podczas wywoływania metod rozszerzenia AsStreamForRead, AsStreamForWrite i AsStream, wynikowy adapter nie przydzieli buforu, a wszystkie wywołania będą manipulować bezpośrednio podstawowym strumieniem środowiska uruchomieniowego Windows.