使用 Windows 应用 SDK 创建 WinUI 应用,以高效访问文件系统,避免磁盘延迟和不必要的内存或 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;
}
第二个示例使用 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;
}
第三个示例使用 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 or set extra properties here.
var systemSize = propertyResults["System.Size"];
}
如果要对 Windows.Storage 对象(例如 Windows.Storage.ApplicationData.Current.LocalFolder)执行多个操作,请创建一个本地变量来引用该存储源,以便在每次访问存储源时不要重新创建中间对象。
C# 中的流性能
在 Windows 运行时和 .NET 流之间缓冲
你可能希望将 Windows 运行时流(例如 Windows.Storage.Streams.IInputStream 或 IOutputStream)转换为 .NET 流(System.IO.Stream)时,有许多方案。 例如,当你编写 WinUI 应用并希望使用使用 Windows 存储 API 处理流的现有 .NET 代码时,这非常有用。 若要启用此功能,.NET 提供了扩展方法,可用于在 .NET 和 Windows 运行时流类型之间进行转换。 有关详细信息,请参阅 WindowsRuntimeStreamExtensions。
将 Windows 运行时流转换为 .NET 流时,可以有效地为基础 Windows 运行时流创建适配器。 在某些情况下,运行时成本与在 Windows 运行时流上调用方法相关。 这可能会影响应用的速度,尤其是在执行许多小型、频繁的读取或写入操作的情况下。
为了帮助加快应用速度,Windows 运行时流适配器包含数据缓冲区。 下面的代码示例演示了使用具有默认缓冲区大小的 Windows 运行时流适配器进行小型连续读取。
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);
}
在将 Windows 运行时流转换为 .NET 流的大部分情况下,此默认缓冲行为是可取的。 但是,在某些情况下,可能需要调整缓冲行为以提高性能。
使用大型数据集
读取或写入较大的数据集时,可以通过向 AsStreamForRead、AsStreamForWrite 和 AsStream 扩展方法提供较大的缓冲区大小来提高读取或写入吞吐量。 这为流适配器提供更大的内部缓冲区大小。 例如,将来自大型文件的流传递到 XML 分析器时,分析器可以从流中执行许多连续的小读取。 大型缓冲区可以减少对基础 Windows 运行时流的调用数并提高性能。
注释
在设置大于约 80 KB 的缓冲区大小时要小心,因为这可能会导致垃圾回收器堆上的内存碎片。 有关相关指南,请参阅 改进 WinUI 应用中的垃圾回收性能。 下面的代码示例使用 81,920 字节缓冲区创建托管流适配器。
// Create a stream adapter with an 80 KB buffer.
Stream managedStream = 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);
此示例使用 1 MB 的缓冲区大小,该大小大于以前建议的 80 KB。 使用此类大型缓冲区可以提高非常大数据集(即几百兆字节)复制操作的吞吐量。 但是,此缓冲区是在大型对象堆上分配的,可能会降低垃圾回收性能。 仅当大型缓冲区大小明显提高应用性能时,才应使用大缓冲区大小。
同时处理大量流时,您可能希望减少或消除缓冲区的内存开销。 可以指定较小的缓冲区,或将 bufferSize 参数设置为 0,以完全关闭该流适配器的缓冲。 如果对托管流执行大型读取和写入操作,仍可以实现良好的吞吐量性能,而无需缓冲。
执行延迟敏感操作
如果您希望实现低延迟读写,并且避免从底层 Windows 运行时流中读取大型数据块,那么您可能还想避免使用缓冲。 例如,如果您在网络通信中使用流,您可能需要低延迟的读取和写入操作。
在聊天应用中,可以使用通过网络接口的流来回发送消息。 在这种情况下,你想要在消息准备就绪后立即发送消息,而不是等待缓冲区填满。 如果在调用 AsStreamForRead、AsStreamForWrite 和 AsStream 扩展方法时将缓冲区大小设置为 0,则生成的适配器将不会分配缓冲区,并且所有调用都将直接操作基础 Windows 运行时流。