Подтвердить что ты не робот

Асинхронное копирование файлов/перемещение в С#

Каков правильный способ копирования или перемещения файла в С#?

4b9b3361

Ответ 1

Идея асинхронного программирования состоит в том, чтобы позволить вызывающему потоку (при условии, что он является потоком пула потоков) возвращаться в пул потоков для использования в какой-либо другой задаче, в то время как async IO завершается. Под капотом контекст вызова заполняется в структуру данных, и один или несколько потоков завершения ввода-вывода контролируют ожидающий завершения вызов. Когда IO завершает поток завершения, возвращается обратно в пул потоков, восстанавливая контекст вызова. Таким образом, вместо 100 блокировок потоков есть только потоки завершения и несколько потоков пулов потоков, которые почти не работают.

Лучшее, что я могу придумать, это:

public async Task CopyFileAsync(string sourcePath, string destinationPath)
{
  using (Stream source = File.Open(sourcePath))
  {
    using(Stream destination = File.Create(destinationPath))
    {
      await source.CopyToAsync(destination);
    }
  }
}

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

Ожидание делает то, что я описываю за кулисами. Если вы хотите получить общее представление о том, как это работает, это, вероятно, поможет понять Джеффа Рихтера AsyncEnumerator. Они могут быть не совсем одинаковыми линиями, но идеи действительно близки. Если вы когда-либо смотрите на стек вызовов из метода "async", вы увидите MoveNext на нем.

Что касается перемещения, ему не нужно быть асинхронным, если это действительно "Переместить", а не копировать, а затем удалить. Перемещение - это быстрая атомная операция против таблицы файлов. Это работает только так, но если вы не пытаетесь переместить файл в другой раздел.

Ответ 2

Здесь используется метод копирования асинхронного файла, который дает подсказки ОС, которые мы читаем и записываем последовательно, чтобы он мог предварительно извлекать данные для чтения и готовить все для записи:

public static async Task CopyFileAsync(string sourceFile, string destinationFile)
{
    using (var sourceStream = new FileStream(sourceFile, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, FileOptions.Asynchronous | FileOptions.SequentialScan))
    using (var destinationStream = new FileStream(destinationFile, FileMode.CreateNew, FileAccess.Write, FileShare.None, 4096, FileOptions.Asynchronous | FileOptions.SequentialScan))
        await sourceStream.CopyToAsync(destinationStream);
}

Вы также можете поэкспериментировать с размером буфера. Вот он 4096 байт.

Ответ 3

Я немного улучшил код с помощью @DrewNoakes (производительность и отмена):

  public static async Task CopyFileAsync(string sourceFile, string destinationFile, CancellationToken cancellationToken)
  {
     var fileOptions = FileOptions.Asynchronous | FileOptions.SequentialScan;
     var bufferSize = 4096;

     using (var sourceStream = 
           new FileStream(sourceFile, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, fileOptions))

     using (var destinationStream = 
           new FileStream(destinationFile, FileMode.CreateNew, FileAccess.Write, FileShare.None, bufferSize, fileOptions))

        await sourceStream.CopyToAsync(destinationStream, bufferSize, cancellationToken)
                                   .ConfigureAwait(continueOnCapturedContext: false);
  }

Ответ 4

Вы можете использовать асинхронные делегаты

public class AsyncFileCopier
    {
        public delegate void FileCopyDelegate(string sourceFile, string destFile);

        public static void AsynFileCopy(string sourceFile, string destFile)
        {
            FileCopyDelegate del = new FileCopyDelegate(FileCopy);
            IAsyncResult result = del.BeginInvoke(sourceFile, destFile, CallBackAfterFileCopied, null);
        }

        public static void FileCopy(string sourceFile, string destFile)
        { 
            // Code to copy the file
        }

        public static void CallBackAfterFileCopied(IAsyncResult result)
        {
            // Code to be run after file copy is done
        }
    }

Вы можете назвать это как:

AsyncFileCopier.AsynFileCopy("abc.txt", "xyz.txt");

Эта ссылка сообщает вам различные методы кодирования asyn

Ответ 5

Вы можете сделать это как this, предложенная статьей:

public static void CopyStreamToStream(
    Stream source, Stream destination,
    Action<Stream, Stream, Exception> completed)
    {
        byte[] buffer = new byte[0x1000];
        AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(null);

        Action<Exception> done = e =>
        {
            if(completed != null) asyncOp.Post(delegate
                {
                    completed(source, destination, e);
                }, null);
        };

        AsyncCallback rc = null;
        rc = readResult =>
        {
            try
            {
                int read = source.EndRead(readResult);
                if(read > 0)
                {
                    destination.BeginWrite(buffer, 0, read, writeResult =>
                    {
                        try
                        {
                            destination.EndWrite(writeResult);
                            source.BeginRead(
                                buffer, 0, buffer.Length, rc, null);
                        }
                        catch(Exception exc) { done(exc); }
                    }, null);
                }
                else done(null);
            }
            catch(Exception exc) { done(exc); }
        };

        source.BeginRead(buffer, 0, buffer.Length, rc, null);

Ответ 6

В то время как есть некоторые обстоятельства, когда вы хотите избежать Task.Run, Task.Run(() => File.Move(source, dest) будет работать. Это стоит учитывать, потому что, когда файл просто перемещается на том же диске/томе, это почти мгновенная операция, так как заголовки изменены, но содержимое файла не перемещается. Различные "чистые" асинхронные методы неизменно копируют поток, даже если нет необходимости делать это, и в результате на практике может быть немного медленнее на практике.

Ответ 7

AFAIK, асинхронный API высокого уровня не копирует файл. Однако вы можете создать свой собственный API для выполнения этой задачи с помощью API Stream.BeginRead/EndRead и Stream.BeginWrite/EndWrite. В качестве альтернативы вы можете использовать метод BeginInvoke/EndInvoke, как указано в ответах здесь, но вы должны иметь в виду, что они не будут блокировать асинхронный ввод-вывод. Они просто выполняют задачу в отдельном потоке.

Ответ 8

Правильный способ копирования: использовать отдельный поток.

Вот как вы могли бы это сделать (синхронно):

//.. [code]
doFileCopy();
// .. [more code]

Здесь, как сделать это асинхронно:

// .. [code]
new System.Threading.Thread(doFileCopy).Start();
// .. [more code]

Это очень наивный способ сделать что-то. Совершенно хорошо, решение будет включать в себя некоторый метод event/delegate для отчета о статусе копии файла и уведомления о важных событиях, таких как сбой, завершение и т.д.

веселит, JRH

Ответ 9

Я бы предположил, что функция File Copy IO, доступная на языках программирования .Net, в любом случае является асинхронной. После использования его в моей программе для перемещения небольших файлов, кажется, что последующие инструкции начинают выполняться до завершения фактической копии файла. Я разбираюсь в том, что исполняемый файл дает Windows задачу сделать копию, а затем сразу же возвращается для выполнения следующей инструкции - не дожидаясь завершения Windows. Это заставляет меня конструировать в цикле сразу после вызова для копирования, который будет выполняться, пока я не смогу подтвердить, что копия завершена.