Самый быстрый способ переместить папку в другое место в UWP
У меня есть 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
1 ответ
Есть несколько проблем с кодом, который вы разместили:
Вы запускаете операции файлового ввода-вывода одну за другой и ждете их завершения. Поскольку файловый ввод / вывод в UWP осуществляется через посредников, это включает вызов в другой процесс. Так как большая часть времени тратится на обмен данными между процессами, вы становитесь узким местом ваших собственных ожиданий. Ваш диск вообще не активен в это время.
WinRT File I/O API очень эффективен с точки зрения производительности. Вы хотите избежать этого как можно больше. Поскольку у вас есть правильный доступ к исходному пути, вы должны использовать класс 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
}
Чтобы повысить производительность вашего кода, вы можете попробовать перечислить все файлы в папке и подпапках сразу, а не по всей структуре папок (папка за папкой):
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()
способ скопировать папку в новое место и удалить оригинал (это даже предлагается в Документах):
В настоящее время нет метода MoveAsync или аналогичного метода. Одна из простых реализаций перемещения папки может состоять в том, чтобы получить нужную папку, скопировать ее в нужное место и затем удалить исходную папку.
Однако стоимость дополнительного дискового пространства не очень привлекательна и может даже не повысить производительность, поскольку копирование обходится дороже, чем перемещение.
UWP в настоящее время не имеет ничего подобного MoveAsync
по состоянию на август 2019 г. Этот ответ обеспечивает поведение, аналогичное функции MoveAsync, и предполагает, что вы работаете за пределами изолированной программной среды / локального состояния приложения UWP, поскольку в изолированной программной среде вы можете использовать классическую среду намного быстрее System.IO
методы из.NET. Просто используйте последний в своей песочнице, иначе вы можете использовать это специальное:
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);
}
}