C#中的异步文件复制/移动

use*_*883 63 c# asynchronous

在C#中异步执行文件复制/移动的正确方法是什么?

csa*_*aam 48

异步编程的想法是允许调用线程(假设它是一个线程池线程)返回到线程池,以便在异步IO完成时用于某些其他任务.在引擎盖下,调用上下文被填充到数据结构中,并且一个或多个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);
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

我没有对此进行过广泛的性能测试.我有点担心,因为如果它很简单就已经存在于核心库中了.

等待我在幕后所做的事情.如果你想了解它是如何工作的,那么理解Jeff Richter的AsyncEnumerator可能会有所帮助.它们可能不是完全相同的线,但想法非常接近.如果您从"异步"方法查看调用堆栈,您将在其上看到MoveNext.

就移动而言,它不需要是异步的,如果它真的是"移动"而不是副本然后删除.Move是对文件表的快速原子操作.如果您不尝试将文件移动到其他分区,它只会以这种方式工作.

  • 请注意,如果您没有显式打开具有特定提示的文件,您将异步使用它(并且这不会这样做),那么幕后发生的事情归结为线程上的同步写入池.请参阅DrewNoakes的答案,了解提供此类提示的内容. (7认同)
  • 使这些方法"异步"必须像建立f $和死星一样困难......这个答案已经有2年了......而且没有任何改变!没有`File.CopyAsync`,没有`File.GetInfoAsync`,没有`Directory.EnumerateAsync`. (5认同)
  • 在内部,标记为aync的方法中的等待是等待等待的代码拼凑完成.天真的我们可以说它阻止了.但它并没有真正阻止.像Wait()这样的实际阻塞行为会使活动线程停留在执行点.Await实际上导致线程正在做的任何事情的上下文被卡入数据结构,并允许活动线程返回到线程池,在那里它可以用于其他东西.当await确实返回一个线程池线程(可能不是同一个线程)时,检索上下文并继续执行. (2认同)
  • 如果有人担心这个.微软有一个相同代码的例子,所以我认为它必须是合法的:https://msdn.microsoft.com/en-us/library/hh159084(v = vs.110).aspx (2认同)

Dre*_*kes 26

这是一个异步文件复制方法,它给出了我们正在按顺序读取和写入的操作系统提示,以便它可以预读取读取数据并为写入做好准备:

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);
}
Run Code Online (Sandbox Code Playgroud)

您也可以尝试缓冲区大小.这是4096字节.


Gre*_*egC 11

我稍微增强了@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);
  }
Run Code Online (Sandbox Code Playgroud)

  • 这可能会产生误导,如果我们正在开发 gui 应用程序,我们希望返回到捕获的上下文。这应该由用户决定,更高级别(`await CopyFileAsync().ConfigureAwait(false)` (4认同)
  • 在“CopyToAsync”中将缓冲区大小设置为 4096 会大大降低写入网络共享时的速度。使用默认的 81920 是一个更好的选择,在我的例子中,速度从 2 Mbps 到 25 Mbps。有关解释,请参阅 [此相关问题](/sf/ask/1021124611/)。 (4认同)
  • @Nekromancer实际上,这里使用`await sourceStream.CopyToAsync().ConfigureAwait(false)`是正确的,因为其余的方法代码(无)不关心它在哪个上下文中运行。您的调用方法使用它自己的“await CopyFileAsync()”和它自己的“ConfigureAwait()”,如果未显式设置,它将设置为“true”。 (3认同)

Cas*_*sey 8

虽然在某些情况下您需要避免Task.Run,但是Task.Run(() => File.Move(source, dest)会有效.值得考虑的是因为当文件只是在同一磁盘/卷中移动时,它几乎是瞬时操作,因为标题被更改但文件内容不会被移动.各种"纯"异步方法总是复制流,即使没有必要这样做,因此在实践中可能会慢得多.

  • @IllidanS4 不幸的是,如果您的文件足够大,我们可能会讨论节省几分钟的时间。 (2认同)
  • -1。移动和复制是两个不同的操作,OP专门询问了后者,这不涉及源文件的消失。 (2认同)
  • @TomLint如果您查看编辑历史记录,您会发现问题最初是“如何复制/移动文件”。我无法控制在我回答四年后有人改变了这个问题。 (2认同)

Ras*_*dit 6

您可以使用异步委托

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
        }
    }
Run Code Online (Sandbox Code Playgroud)

您可以将其命名为:

AsyncFileCopier.AsynFileCopy("abc.txt", "xyz.txt");
Run Code Online (Sandbox Code Playgroud)

链接告诉您asyn编码的不同技术

  • 我认为问题是异步执行操作,而不消耗线程.有多种方法可以将工作委托给线程池,其中大多数方法比这里的机制更容易. (4认同)

Pab*_*tyk 5

您可以按照本文的建议执行此操作:

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);
Run Code Online (Sandbox Code Playgroud)

  • Streams 现在有一个内置的复制操作,使这更容易。但是我对这种技术的问题是它总是复制文件,即使它在同一个磁盘上并且不需要这样的操作。 (2认同)

jrh*_*ath -7

正确的复制方式:使用单独的线程。

您可能会这样做(同步):

//.. [code]
doFileCopy();
// .. [more code]
Run Code Online (Sandbox Code Playgroud)

以下是异步执行的方法:

// .. [code]
new System.Threading.Thread(doFileCopy).Start();
// .. [more code]
Run Code Online (Sandbox Code Playgroud)

这是一种非常幼稚的做事方式。如果做得好,该解决方案将包括一些事件/委托方法来报告文件副本的状态,并通知重要事件,例如失败、完成等。

干杯,jrh