如何在不阻塞线程的情况下用C#复制文件?
Answers:
异步编程的思想是允许调用线程(假设它是线程池线程)返回到线程池以在异步IO完成时用于其他任务。在幕后,调用上下文填充到数据结构中,并且有1个或多个IO完成线程监视等待完成的调用。当IO完成时,完成线程将调用回到恢复调用上下文的线程池中。这样,只有100个完成线程和几个线程池线程(大部分处于空闲状态),而不是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);
}
}
}
不过,我尚未对此进行全面的性能测试。我有点担心,因为如果这么简单,它将已经存在于核心库中。
等待做我在幕后描述的事情。如果您想对它的工作原理有一个大概的了解,可能会有助于理解Jeff Richter的AsyncEnumerator。他们可能并不完全相同,但是想法确实很接近。如果您通过“异步”方法查看调用堆栈,则会在其上看到MoveNext。
就移动而言,如果它确实是“移动”而不是复制然后删除,则不需要异步。移动是针对文件表的快速原子操作。即使您不尝试将文件移动到其他分区,它也只能以这种方式工作。
async
必须与建立f $&#ing死亡之星一样困难。这个答案已经有2年了……而且什么都没有改变!没有File.CopyAsync
,没有File.GetInfoAsync
,没有Directory.EnumerateAsync
。
这是一个异步文件复制方法,该方法向操作系统提示我们正在按顺序读取和写入数据,以便它可以预读取读取的数据并为写入做好准备:
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);
}
await CopyFileAsync().ConfigureAwait(false)
CopyToAsync
大大降低写入网络共享时的速度。使用默认值81920是更好的选择,在我的情况下,速度从2 Mbps变为25 Mbps。请参阅此相关问题以获取解释。
await sourceStream.CopyToAsync().ConfigureAwait(false)
此处是正确的,因为其余的方法代码(无关)并不关心它在哪个上下文中运行。您的调用方法使用它自己的await CopyFileAsync()
和它自己的ConfigureAwait()
,true
如果未明确设置,则将设置为。
在某些情况下,您想避免时Task.Run
,它Task.Run(() => File.Move(source, dest)
会起作用。值得考虑的是,当简单地将文件移动到相同的磁盘/卷中时,这是几乎瞬时的操作,因为更改了标头但不移动文件内容。即使不需要这样做,各种“纯”异步方法也总是复制流,因此在实践中可能会慢很多。
您可以使用异步委托
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。他们仅在单独的线程上执行任务。