【问题标题】:Fastest way to move folder to another place in UWP在 UWP 中将文件夹移动到另一个位置的最快方法
【发布时间】:2019-07-23 08:55:06
【问题描述】:

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

注意extractionFolderApplicationData.Current.LocalCacheFolder 中,targetFolder 是用户通过FolderPicker 选择的任何文件夹

【问题讨论】:

  • 您无法真正改进普通复制过程本身,但您可以并行复制目录。
  • 在windows中我可以简单地剪切/粘贴文件夹,这需要0时间,真可惜,UWP没有移动文件夹的功能? @肖恩
  • AFAIK,由于大量的安全检查等,从 UWP 读取文件应该很慢,你可以做的是,使用完全信任进程启动正常的 win32 exe 并使用它来处理文件读写以避免这些安全检查。
  • 当您运行代码时,您的任何设备是否都在 100% 运行?

标签: c# performance uwp directory storagefolder


【解决方案1】:

为了提高代码的性能,您可以尝试一次枚举文件夹和子文件夹中的所有文件,而不是遍历整个文件夹结构(逐个文件夹):

var results = storageFolder.CreateFileQueryWithOptions(
                  new QueryOptions() { FolderDepth = FolderDepth.Deep } );
var files = (await results.GetFilesAsync()).ToArray();

storageFolder 是您要移动的文件夹。自定义文件查询将FolderDepth 设置设置为Deep,以便它返回整个文件夹结构中的所有文件。运行后,files 数组将包含所有文件,然后您可以移动它们。这至少比一个一个枚举所有文件夹快一点。您只需确保始终检查在目标位置创建了适当的子文件夹。

最后你可以尝试并行移动Tasks - 例如一次移动三个文件。您可以使用Task.WhenAll 创建多个Task 实例和await

复制粘贴解决方案

另一个快速而肮脏的解决方案是使用StorageFolder.CopyAsync() 方法将文件夹复制到新位置并删除原始文件夹(Docs 甚至建议这样做):

目前没有“MoveAsync”或类似方法。移动文件夹的一种简单实现可能是获取所需文件夹,将其复制到所需位置,然后删除原始文件夹。

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

【讨论】:

    【解决方案2】:

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

    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
            }
    

    【讨论】:

    • 提示提示 @Peter Torr - MSFT :)
    • 不幸的是,这与broadFileSystemAccess 不兼容。我会看看是否可以在您使用 .NET FileIO API 的某些地方重新使用 UWP FileIO API。但愿如此。每个文件的 UWP 复制/移动 API 的性能糟糕....
    • 看起来不像。我做了很多改变,但一点击var sourceBytes = File.ReadAllBytes(item.FullName);,我就卡住了。我注释掉了递归,因为我只是想看看我是否可以让快乐的道路首先工作。如果你想接受它并弥补最后的差距,这是我的要点。我不太了解与字节流的接口,而且我越来越困 x_x 哈哈。无论哪种方式都很酷! :) 由于所有额外的异步选项,我什至不确定这是否可以利用您的性能提升,但我愿意对其进行测试 =/
    • 如果我真的离开网址可能会有所帮助.. LOL gist.github.com/the-nose-knows/bc86f2a4e6baa53f96b716178c1dbc24
    • 我的代码只有在目的地位于代理路径中时才能工作,而源不在。如果您的源位于应用程序通常无法访问的路径中,则需要使用IStorageFolderHandleAccess 接口读取它。
    【解决方案3】:

    截至 2019 年 8 月,UWP 目前没有像 MoveAsync 这样的东西。这个答案实现了与 MoveAsync 函数类似的行为,并假设您在 UWP 应用沙箱/本地状态之外工作,因为在沙箱内您可以使用来自 .NET 的经典更快的 System.IO 方法。只需在您的沙箱中使用后者,否则您可以使用这个 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);
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2013-07-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-06-19
      相关资源
      最近更新 更多