简体   繁体   English

C#中的非阻塞文件复制

[英]Non-blocking file copy in C#

如何在不阻塞线程的情况下在 C# 中复制文件?

The idea of async programming is to allow the calling thread (assuming it's a thread pool thread) to return to the thread pool for use on some other task while async IO completes.异步编程的思想是允许调用线程(假设它是线程池线程)在异步 IO 完成时返回线程池以用于其他任务。 Under the hood the call context gets stuffed into a data structure and 1 or more IO completion threads monitor the call waiting for completion.在幕后,调用上下文被填充到一个数据结构中,1 个或多个 IO 完成线程监视等待完成的调用。 When IO completes the completion thread invokes back onto a thread pool restoring the call context.当 IO 完成时,完成线程调用回恢复调用上下文的线程池。 That way instead of 100 threads blocking there is only the completion threads and a few thread pool threads sitting around mostly idle.这样,而不是 100 个线程阻塞,只有完成线程和一些线程池线程大部分闲置。

The best I can come up with is:我能想到的最好的是:

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

I haven't done extensive perf testing on this though.不过,我还没有对此进行广泛的性能测试。 I'm a little worried because if it was that simple it would already be in the core libraries.我有点担心,因为如果它这么简单,它已经在核心库中了。

await does what I am describing behind the scenes. await 做我在幕后描述的事情。 If you want to get a general idea of how it works it would probably help to understand Jeff Richter's AsyncEnumerator.如果您想大致了解它是如何工作的,那么理解 Jeff Richter 的 AsyncEnumerator 可能会有所帮助。 They might not be completely the same line for line but the ideas are really close.它们可能不完全相同,但想法非常接近。 If you ever look at a call stack from an "async" method you'll see MoveNext on it.如果您从“异步”方法查看调用堆栈,您会在其上看到 MoveNext。

As far as move goes it doesn't need to be async if it's really a "Move" and not a copy then delete.就移动而言,如果它确实是“移动”而不是复制然后删除,则它不需要是异步的。 Move is a fast atomic operation against the file table.移动是针对文件表的快速原子操作。 It only works that way though if you don't try to move the file to a different partition.如果您不尝试将文件移动到其他分区,它只会以这种方式工作。

Here's an async file copy method that gives the OS hints that we're reading and writing sequentially, so that it can pre-fetch data on the read and have things ready for the write:这是一个异步文件复制方法,它向操作系统提示我们正在按顺序读取和写入,以便它可以在读取时预取数据并为写入做好准备:

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

You can experiment with the buffer size as well.您也可以试验缓冲区大小。 Here's it's 4096 bytes.这是 4096 字节。

I've enhanced code by @DrewNoakes slightly (performance and cancellation):我通过@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);
  }

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

You can use asynchronous delegates您可以使用异步委托

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
        }
    }

You can call it as:您可以将其称为:

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

This link tells you the different techniques of asyn coding这个链接告诉你异步编码的不同技术

You can do it as this article suggested:你可以做到这一点作为文章建议:

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);

AFAIK, there is no high level async API to copy a file. AFAIK,没有高级异步 API 来复制文件。 However, you can build your own API to accomplish that task using Stream.BeginRead/EndRead and Stream.BeginWrite/EndWrite APIs.但是,您可以使用Stream.BeginRead/EndReadStream.BeginWrite/EndWrite API 构建自己的 API 来完成该任务。 Alternatively, you can use BeginInvoke/EndInvoke method as mentioned in the answers here, but you have to keep in mind, that they won't be non blocking async I/O.或者,您可以使用此处的答案中提到的BeginInvoke/EndInvoke方法,但您必须记住,它们不会是非阻塞异步 I/O。 They merely perform the task on a separate thread.它们只是在单独的线程上执行任务。

I implemented this solution for copying large files (backup files) and it's terribly slow!我实现了这个用于复制大文件(备份文件)的解决方案,它非常慢! For smaller files, it's not a problem, but for large files just use File.Copy or an implementation of robocopy with parameter /mt (multithread).对于较小的文件,这不是问题,但对于大文件,只需使用 File.Copy 或带有参数 /mt(多线程)的 robocopy 的实现。

Note that this, copy file async, is still an open issue for .net development: https://github.com/dotnet/runtime/issues/20695请注意,复制文件异步仍然是 .net 开发的一个悬而未决的问题: https : //github.com/dotnet/runtime/issues/20695

I would suggest that the File Copy IO function, available in the .Net programming languages, is asynchronous in any case.我建议在 .Net 编程语言中可用的 File Copy IO 函数在任何情况下都是异步的。 After using it within my program to move small files, it appears that subsequent instructions begin to execute before the actual file copy is finished.在我的程序中使用它来移动小文件后,似乎在实际文件复制完成之前后续指令开始执行。 I'm gussing that the executable gives Windows the task to do the copy and then immediately returns to execute the next instruction - not waiting for Windows to finish.我猜测可执行文件为 Windows 提供了执行复制的任务,然后立即返回以执行下一条指令——而不是等待 Windows 完成。 This forces me to construct while loops just after the call to copy that will execute until I can confirm the copy is complete.这迫使我在调用 copy 之后构建 while 循环,该循环将执行,直到我确认复制完成。

The correct way to copy: use a separate thread.正确的复制方式:使用单独的线程。

Here's how you might be doing it (synchronously):您可能会这样做(同步):

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

Here's how to do it asynchronously:以下是异步执行的方法:

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

This is a very naive way to do things.这是一种非常幼稚的做事方式。 Done well, the solution would include some event/delegate method to report the status of the file copy, and notify important events like failure, completion etc.做得好,解决方案将包括一些事件/委托方法来报告文件副本的状态,并通知重要事件,如失败、完成等。

cheers, jrh干杯,jrh

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM