我有SSD存储,此代码需要32秒才能将〜200个文件和〜40个文件夹移至相同的存储在调试或发布模式下。文件夹的总尺寸为〜30 mb。
我该如何更快地制作?
// moves content from local folder to target folder.
async Task MoveContent(IStorageFolder source, IStorageFolder destination)
{
foreach(var item in await source.GetItemsAsync())
{
switch (item)
{
case IStorageFile sourceFile:
await sourceFile.MoveAsync(destination, sourceFile.Name, NameCollisionOption.ReplaceExisting);
break;
case IStorageFolder sourceSubFolder:
var destinationSubFolder = await destination.CreateFolderAsync(sourceSubFolder.Name, CreationCollisionOption.ReplaceExisting);
await MoveContent(sourceSubFolder, destinationSubFolder);
break;
}
}
}
我称之为这个
await MoveContent(extractionFolder, targetFolder);
请注意,extractionFolder
在ApplicationData.Current.LocalCacheFolder
中,targetFolder
是用户通过FolderPicker
您发布的代码有几个问题:
您将文件I/O操作驱动一一,然后等待其完成。由于UWP中的文件I/O是经过的,因此涉及致电另一个过程。由于大部分时间都是在过程之间进行交流的,因此您会被自己的等待所束缚。在此期间,您的磁盘根本没有活动。
winrt文件I/O API是明智的垃圾性能。您想尽可能多地避免它。由于您可以正确访问源路径,因此应使用C#DirectoryInfo类列举文件。然后,使用c#file i/o。
而不是使用moveAsync(因为您不再具有源为iStorageiTem)
随着这些更改,IT设法完成了我的合成测试用例(40个文件夹,每个文件中有5个文件)需要300毫秒,而使用您的代码为12秒。那快30倍。如果我们被允许使用Win32 API(例如MoveFile),但不幸的是,这可能会变得更快,但是不幸的是,目前无法使用文件/文件夹选择器选择的文件夹和文件。
这是代码。
async Task MoveContentFast(IStorageFolder source, IStorageFolder destination)
{
await Task.Run(() =>
{
MoveContextImpl(new DirectoryInfo(source.Path), destination);
});
}
private void MoveContextImpl(DirectoryInfo sourceFolderInfo, IStorageFolder destination)
{
var tasks = new List<Task>();
var destinationAccess = destination as IStorageFolderHandleAccess;
foreach (var item in sourceFolderInfo.EnumerateFileSystemInfos())
{
if ((item.Attributes & System.IO.FileAttributes.Directory) != 0)
{
tasks.Add(destination.CreateFolderAsync(item.Name, CreationCollisionOption.ReplaceExisting).AsTask().ContinueWith((destinationSubFolder) =>
{
MoveContextImpl((DirectoryInfo)item, destinationSubFolder.Result);
}));
}
else
{
if (destinationAccess == null)
{
// Slower, pre 14393 OS build path
tasks.Add(WindowsRuntimeStorageExtensions.OpenStreamForWriteAsync(destination, item.Name, CreationCollisionOption.ReplaceExisting).ContinueWith((openTask) =>
{
using (var stream = openTask.Result)
{
var sourceBytes = File.ReadAllBytes(item.FullName);
stream.Write(sourceBytes, 0, sourceBytes.Length);
}
File.Delete(item.FullName);
}));
}
else
{
int hr = destinationAccess.Create(item.Name, HANDLE_CREATION_OPTIONS.CREATE_ALWAYS, HANDLE_ACCESS_OPTIONS.WRITE, HANDLE_SHARING_OPTIONS.SHARE_NONE, HANDLE_OPTIONS.NONE, IntPtr.Zero, out SafeFileHandle file);
if (hr < 0)
Marshal.ThrowExceptionForHR(hr);
using (file)
{
using (var stream = new FileStream(file, FileAccess.Write))
{
var sourceBytes = File.ReadAllBytes(item.FullName);
stream.Write(sourceBytes, 0, sourceBytes.Length);
}
}
File.Delete(item.FullName);
}
}
}
Task.WaitAll(tasks.ToArray());
}
[ComImport]
[Guid("DF19938F-5462-48A0-BE65-D2A3271A08D6")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IStorageFolderHandleAccess
{
[PreserveSig]
int Create(
[MarshalAs(UnmanagedType.LPWStr)] string fileName,
HANDLE_CREATION_OPTIONS creationOptions,
HANDLE_ACCESS_OPTIONS accessOptions,
HANDLE_SHARING_OPTIONS sharingOptions,
HANDLE_OPTIONS options,
IntPtr oplockBreakingHandler,
out SafeFileHandle interopHandle); // using Microsoft.Win32.SafeHandles
}
internal enum HANDLE_CREATION_OPTIONS : uint
{
CREATE_NEW = 0x1,
CREATE_ALWAYS = 0x2,
OPEN_EXISTING = 0x3,
OPEN_ALWAYS = 0x4,
TRUNCATE_EXISTING = 0x5,
}
[Flags]
internal enum HANDLE_ACCESS_OPTIONS : uint
{
NONE = 0,
READ_ATTRIBUTES = 0x80,
// 0x120089
READ = SYNCHRONIZE | READ_CONTROL | READ_ATTRIBUTES | FILE_READ_EA | FILE_READ_DATA,
// 0x120116
WRITE = SYNCHRONIZE | READ_CONTROL | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | FILE_APPEND_DATA | FILE_WRITE_DATA,
DELETE = 0x10000,
READ_CONTROL = 0x00020000,
SYNCHRONIZE = 0x00100000,
FILE_READ_DATA = 0x00000001,
FILE_WRITE_DATA = 0x00000002,
FILE_APPEND_DATA = 0x00000004,
FILE_READ_EA = 0x00000008,
FILE_WRITE_EA = 0x00000010,
FILE_EXECUTE = 0x00000020,
FILE_WRITE_ATTRIBUTES = 0x00000100,
}
[Flags]
internal enum HANDLE_SHARING_OPTIONS : uint
{
SHARE_NONE = 0,
SHARE_READ = 0x1,
SHARE_WRITE = 0x2,
SHARE_DELETE = 0x4
}
[Flags]
internal enum HANDLE_OPTIONS : uint
{
NONE = 0,
OPEN_REQUIRING_OPLOCK = 0x40000,
DELETE_ON_CLOSE = 0x4000000,
SEQUENTIAL_SCAN = 0x8000000,
RANDOM_ACCESS = 0x10000000,
NO_BUFFERING = 0x20000000,
OVERLAPPED = 0x40000000,
WRITE_THROUGH = 0x80000000
}
要改善代码的性能,您可以尝试一次枚举文件夹和子文件夹中的所有文件,而不是在整个文件夹结构中(文件夹):
var results = storageFolder.CreateFileQueryWithOptions(
new QueryOptions() { FolderDepth = FolderDepth.Deep } );
var files = (await results.GetFilesAsync()).ToArray();
其中 storageFolder
是要移动的文件夹。自定义文件查询将FolderDepth
设置设置为Deep
,以便从整个文件夹结构中返回所有文件。运行此操作后,files
数组将包含所有文件,然后您可以移动它们。这将比一一列举所有文件夹要快一点。您只需要确保始终检查在目标位置创建适当的子文件夹即可。
最后,您可以尝试并行化MOVE Tasks
- 例如一次移动三个文件。您可以使用Task.WhenAll
创建多个Task
实例,并使用await
。
复制 - 帕斯特解决方案
另一个快速而肮脏的解决方案将是使用StorageFolder.CopyAsync()
方法将文件夹复制到新位置并删除原件(甚至在文档中建议):
当前没有" moveasync"或类似方法。移动文件夹的一个简单实现可能是获取所需的文件夹,将其复制到所需的位置,然后删除原始文件夹。
但是,额外存储空间的成本不是很吸引人,甚至可能不会提高性能,因为复制比移动更为昂贵。
uwp当前没有MoveAsync
之类的东西。截至2019年8月。此答案的行为与moveAsync函数相似,并且假设您在UWP应用程序sandbox/local中工作声明,因为在沙箱中,您可以使用.NET使用经典的System.IO
方法。只需在沙箱中使用后者,否则您可以使用此临时:
public static async Task Move_Directory_Async(
StorageFolder sourceDir,
StorageFolder destParentDir,
CreationCollisionOption repDirOpt,
NameCollisionOption repFilesOpt)
{
try
{
if (sourceDir == null)
return;
List<Task> copies = new List<Task>();
var files = await sourceDir.GetFilesAsync();
if (files == null || files.Count == 0)
await destParentDir.CreateFolderAsync(sourceDir.Name);
else
{
await destParentDir.CreateFolderAsync(sourceDir.Name, repDirOpt);
foreach (var file in files)
copies.Add(file.CopyAsync(destParentDir, file.Name, repFilesOpt).AsTask());
}
await sourceDir.DeleteAsync(StorageDeleteOption.PermanentDelete);
await Task.WhenAll(copies);
}
catch(Exception ex)
{
//Handle any needed cleanup tasks here
throw new Exception(
$"A fatal exception triggered within Move_Directory_Async:rn{ex.Message}", ex);
}
}