Non-blocking file copy in C#
How can I copy a file in C# without blocking a thread?
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. 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. When IO completes the completion thread invokes back onto a thread pool restoring the call context. That way instead of 100 threads blocking there is only the completion threads and a few thread pool threads sitting around mostly idle.
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. If you want to get a general idea of how it works it would probably help to understand Jeff Richter's 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.
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.
I've enhanced code by @DrewNoakes slightly (performance and cancellation):
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);
}