最佳化檔案存取
建立可有效存取檔案系統的通用 Windows 平台 (UWP) 應用程式,避免因為磁碟延遲和記憶體/CPU 週期而發生效能問題。
當您想要存取大量檔案,並且想要存取一般 Name、FileType 和 Path 屬性以外的屬性值時,請建立 QueryOptions 並呼叫 SetPropertyPrefetch 來存取這些檔案。 SetPropertyPrefetch 方法可以大幅改善顯示從檔案系統所取得項目集合的應用程式效能,例如影像集合。 下一組範例示範幾個存取多個檔案的方式。
第一個範例會使用 Windows.Storage.StorageFolder.GetFilesAsync 來擷取一組檔案的名稱資訊。 此方法提供良好的效能,因為範例只會存取 name 屬性。
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
第二個範例會使用 Windows.Storage.StorageFolder.GetFilesAsync,然後擷取每個檔案的映像屬性。 此方法提供效能不佳。
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
第三個範例會使用 QueryOptions 來取得一組檔案的相關資訊。 此方法提供比上一個範例更好的效能。
// 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
如果您要在 Windows.Storage 物件上執行多個作業,例如 Windows.Storage.ApplicationData.Current.LocalFolder
,請建立區域變數來參考該儲存體來源,如此一來,您就不會在每次存取時重新建立中繼物件。
C# 與 Visual Basic 中的串流效能
UWP 與 .NET 串流之間的緩衝
您在許多案例中可能需要將 UWP 串流 (例如 Windows.Storage.Streams.IInputStream 或 IOutputStream) 轉換成 .NET 串流 (System.IO.Stream)。 例如,當您撰寫 UWP 應用程式,並想要使用現有的 .NET 程式碼搭配 UWP 檔案系統處理串流時,這會很有用。 若要啟用此功能,適用於 UWP 應用程式的 .NET API 會提供延伸方法,讓您在 .NET 與 UWP 資料流類型之間進行轉換。 如需詳細資訊,請參閱 WindowsRuntimeStreamExtensions。
當您將 UWP 串流轉換成 .NET 串流時,您可以有效地建立基礎 UWP 串流的配接器。 在某些情況下,與在 UWP 串流上叫用方法相關聯的執行階段成本。 這可能會影響應用程式的速度,特別是在您執行許多小型、頻繁讀取或寫入作業的案例。
為了加速應用程式,UWP 串流配接器包含資料緩衝區。 下列程式碼範例示範使用具有預設緩衝區大小的 UWP 串流配接器進行小型連續讀取。
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
在將 UWP 串流轉換成 .NET 串流的大部分案例中,這都是理想的預設緩衝行為。 不過,在某些情況下,您可能需要調整緩衝行為,以提高效能。
使用大型資料集
讀取或寫入較大的資料集時,您可以向 AsStreamForRead、AsStreamForWrite 和 AsStream 擴充方法提供大型緩衝區大小,以增加讀取或寫入輸送量。 這會為串流配接器提供較大的內部緩衝區大小。 例如,將來自大型檔案的串流傳遞至 XML 剖析器時,剖析器可以從串流進行許多循序小型讀取。 大型緩衝區可以減少對基礎 UWP 串流的呼叫數目,並提升效能。
注意 在設定大於約 80 KB 的緩衝區大小時,請務必謹慎,因為這可能導致記憶體回收行程堆積分散 (請參閱改善記憶體回收效能)。 下列程式碼範例會建立具有 81,920 位元組緩衝區的受控串流配接器。
// 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)
Stream.CopyTo 和 CopyToAsync 方法也會配置本機緩衝區,以在串流之間複製。 如同 AsStreamForRead 擴充方法,您可以覆寫預設緩衝區大小,以取得大型串流複本的較佳效能。 下列程式碼範例示範如何變更 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)
此範例使用 1 MB 的緩衝區大小,其大於先前建議的 80 KB。 使用這類大型緩衝區可以改善非常大型資料集的複製作業輸送量 (也就是數百 MB)。 不過,此緩衝區會配置在大型物件堆積上,且可能會降低廢棄項目收集效能。 只有在大幅改善應用程式的效能時,才應該使用大型緩衝區大小。
當您同時使用大量串流時,可能會需要減少或消除緩衝區的記憶體額外負荷。 您可以指定較小的緩衝區,或將 bufferSize 參數設定為 0,以完全關閉該串流配接器的緩衝處理。 如果您對受控串流執行大型讀取和寫入,仍可以在不需要緩衝處理的情況下達到良好的輸送量效能。
執行延遲敏感性作業
如果您想要低延遲讀取和寫入,且不想在基礎 UWP 串流外的大型區塊中讀取,則也可以避免緩衝處理。 例如,如果您使用串流進行網路通訊,可能會需要要低延遲讀取和寫入。
在聊天應用程式中,您可以使用透過網路介面的串流來回傳送訊息。 在此情況下,您需要在訊息就緒後立即傳送訊息,而不是等待緩衝區填滿。 如果您在呼叫 AsStreamForRead、AsStreamForWrite 和 AsStream 擴充方法時將緩衝區大小設定為 0,則產生的配接器將不會配置緩衝區,且所有呼叫都會直接操作基礎 UWP 串流。