将文件夹移动到UWP中另一个位置的最快方法

时间:2019-03-01 10:28:52

标签: c# performance uwp directory storagefolder

我有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选择的任何文件夹

3 个答案:

答案 0 :(得分:1)

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

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

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

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

复制粘贴解决方案

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

  

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

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

答案 1 :(得分:0)

您发布的代码存在几个问题:

  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
        }

答案 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);
    }
}