[英]Non-blocking file copy in C#
如何在不阻塞线程的情况下在 C# 中复制文件?
异步编程的思想是允许调用线程(假设它是线程池线程)在异步 IO 完成时返回线程池以用于其他任务。 在幕后,调用上下文被填充到一个数据结构中,1 个或多个 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);
}
}
}
不过,我还没有对此进行广泛的性能测试。 我有点担心,因为如果它这么简单,它已经在核心库中了。
await 做我在幕后描述的事情。 如果您想大致了解它是如何工作的,那么理解 Jeff Richter 的 AsyncEnumerator 可能会有所帮助。 它们可能不完全相同,但想法非常接近。 如果您从“异步”方法查看调用堆栈,您会在其上看到 MoveNext。
就移动而言,如果它确实是“移动”而不是复制然后删除,则它不需要是异步的。 移动是针对文件表的快速原子操作。 如果您不尝试将文件移动到其他分区,它只会以这种方式工作。
这是一个异步文件复制方法,它向操作系统提示我们正在按顺序读取和写入,以便它可以在读取时预取数据并为写入做好准备:
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 字节。
我通过@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,这是一个几乎是即时的操作,因为标题被更改但文件内容没有移动。各种“纯”异步方法总是复制流,即使不需要这样做,结果在实践中可能会慢一些。
您可以使用异步委托
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");
这个链接告诉你异步编码的不同技术
你可以做到这一点作为此文章建议:
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,没有高级异步 API 来复制文件。 但是,您可以使用Stream.BeginRead/EndRead
和Stream.BeginWrite/EndWrite
API 构建自己的 API 来完成该任务。 或者,您可以使用此处的答案中提到的BeginInvoke/EndInvoke
方法,但您必须记住,它们不会是非阻塞异步 I/O。 它们只是在单独的线程上执行任务。
我实现了这个用于复制大文件(备份文件)的解决方案,它非常慢! 对于较小的文件,这不是问题,但对于大文件,只需使用 File.Copy 或带有参数 /mt(多线程)的 robocopy 的实现。
请注意,复制文件异步仍然是 .net 开发的一个悬而未决的问题: https : //github.com/dotnet/runtime/issues/20695
我建议在 .Net 编程语言中可用的 File Copy IO 函数在任何情况下都是异步的。 在我的程序中使用它来移动小文件后,似乎在实际文件复制完成之前后续指令开始执行。 我猜测可执行文件为 Windows 提供了执行复制的任务,然后立即返回以执行下一条指令——而不是等待 Windows 完成。 这迫使我在调用 copy 之后构建 while 循环,该循环将执行,直到我确认复制完成。
正确的复制方式:使用单独的线程。
您可能会这样做(同步):
//.. [code]
doFileCopy();
// .. [more code]
以下是异步执行的方法:
// .. [code]
new System.Threading.Thread(doFileCopy).Start();
// .. [more code]
这是一种非常幼稚的做事方式。 做得好,解决方案将包括一些事件/委托方法来报告文件副本的状态,并通知重要事件,如失败、完成等。
干杯,jrh
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.