我有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
选择的任何文件夹
答案 0 :(得分:1)
要提高代码的性能,您可以尝试一次枚举文件夹和子文件夹中的所有文件,而不是整个文件夹结构(逐个文件夹):
var results = storageFolder.CreateFileQueryWithOptions(
new QueryOptions() { FolderDepth = FolderDepth.Deep } );
var files = (await results.GetFilesAsync()).ToArray();
storageFolder
是您要移动的文件夹。自定义文件查询的FolderDepth
设置设置为Deep
,以便它返回整个文件夹结构中的所有文件。运行此命令后,files
数组将包含所有文件,然后可以移动它们。这将比枚举所有文件夹至少快一点。您只需要确保始终检查在目标位置创建的适当子文件夹即可。
最后,您可以尝试并行化移动Tasks
-例如一次移动三个文件。您可以创建多个Task
实例,并使用await
来Task.WhenAll
个实例。
复制粘贴解决方案
另一种快速而肮脏的解决方案是使用StorageFolder.CopyAsync()
方法将文件夹复制到新位置并删除原始位置(甚至在Docs中建议):
当前没有“ MoveAsync”或类似方法。移动文件夹的一种简单实现方式可能是获取所需的文件夹,将其复制到所需的位置,然后删除原始文件夹。
但是,额外的存储空间的成本并不是很吸引人,甚至可能无法提高性能,因为复制比移动更昂贵。
答案 1 :(得分:0)
您发布的代码存在几个问题:
您将逐一触发文件I / O操作,然后等待它们完成。由于UWP中的文件I / O是代理的,因此涉及调用另一个进程。由于大多数时间都花在进程之间的通信上,因此您自己的等待会成为瓶颈。您的磁盘在此期间完全不活动。
WinRT文件I / O API绝对是垃圾性能。您想尽可能地避免它。由于您具有对源路径的正确访问权限,因此应使用C#DirectoryInfo类枚举文件。然后,不要使用MoveAsync(因为不再有源作为IStorageItem),请使用C#文件I / O。
通过这些更改,它设法完成了我的综合测试用例(40个文件夹,每个文件夹中有5个文件)需要300毫秒,而使用您的代码需要12秒钟。那快了30倍。如果允许我们使用像MoveFile这样的Win32 API,这样做可能会快得多,但是不幸的是,当前无法对文件/文件夹选择器选择的文件夹和文件执行此操作。
这是代码。
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
}
答案 2 :(得分:0)
截至2019年8月,UWP目前没有类似MoveAsync
的东西。此答案的行为与MoveAsync函数类似,并且假定您在UWP App沙箱/本地状态之外工作,因为沙箱中,您可以使用.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:\r\n{ex.Message}", ex);
}
}