2

我有 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

4

3 回答 3

3

您发布的代码有几个问题:

  1. 您一个接一个地触发文件 I/O 操作,并等待它们完成。由于 UWP 中的文件 I/O 是代理的,这涉及调用另一个进程。由于大部分时间都花在进程之间的通信上,因此您自己的等待会成为瓶颈。在此期间,您的磁盘根本不活动。

  2. 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
        }
于 2019-03-03T06:52:49.570 回答
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”或类似方法。移动文件夹的一种简单实现可能是获取所需文件夹,将其复制到所需位置,然后删除原始文件夹。

但是,额外存储空间的成本并不是很吸引人,甚至可能不会提高性能,因为复制比移动成本更高。

于 2019-03-01T12:27:05.950 回答
0

UWP 目前没有任何类似MoveAsync截至 2019 年 8 月的东西。此答案实现了与 MoveAsync 函数类似的行为,并假设您在 UWP 应用沙箱/本地状态之外工作,因为在沙箱中您可以更快地使用经典System.IO来自 .NET 的方法。只需在您的沙箱中使用后者,否则您可以使用这个 ad-hoc:

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);
    }
}
于 2019-08-17T02:53:41.867 回答