Самый быстрый способ переместить папку в другое место в UWP - PullRequest
0 голосов
/ 01 марта 2019

У меня есть SSD-хранилище, и этот код занимает 32 секунды для перемещения ~ 200 файлов и ~ 40 папок в то же хранилище в режиме отладки или выпуска.общий размер папки ~ 30 МБ.

Как я могу сделать это быстрее?

// 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 голосов
/ 03 марта 2019

В коде, который вы опубликовали, есть несколько проблем:

  1. Вы запускаете операции ввода-вывода файла один за другим и ждете их завершения.Поскольку файловый ввод / вывод в UWP осуществляется через посредников, это включает вызов в другой процесс.Так как большая часть времени тратится на обмен данными между процессами, вы становитесь узким местом ваших собственных ожиданий.Ваш диск вообще не активен в течение этого времени.

  2. API ввода / вывода файлов WinRT является абсолютно бесполезным по производительности.Вы хотите избежать этого как можно больше.Поскольку у вас есть правильный доступ к исходному пути, вы должны использовать класс C # DirectoryInfo для перечисления файлов.Затем, вместо использования MoveAsync (поскольку у вас больше нет источника в качестве IStorageItem), используйте C / File File I / O.

С этими изменениями ему удается завершить мой синтетический тестовый пример (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
        }
0 голосов
/ 17 августа 2019

UWP в настоящее время не имеет ничего похожего на MoveAsync по состоянию на август 2019. Этот ответ демонстрирует поведение, аналогичное функции MoveAsync, и предполагает, что вы работаете вне своей изолированной программной среды UWP App / Local State, поскольку в этой изолированной программной средеВы можете использовать классические гораздо более быстрые 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);
    }
}
0 голосов
/ 01 марта 2019

Чтобы повысить производительность вашего кода, вы можете попробовать перечислить все файлы в папке и подпапках одновременно вместо всей структуры папок (папка за папкой):

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 или аналогичногометод.Одна из простых реализаций перемещения папки может состоять в том, чтобы получить нужную папку, скопировать ее в нужное место и затем удалить исходную папку.

Однако стоимость дополнительного места для хранения не очень привлекательна.и может даже не улучшить производительность, потому что копирование обходится дороже, чем перемещение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...