C # 中的非阻塞文件复制

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

79260 次浏览

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

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. However, you can build your own API to accomplish that task using Stream.BeginRead/EndRead and Stream.BeginWrite/EndWrite APIs. 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. They merely perform the task on a separate thread.

I would suggest that the File Copy IO function, available in the .Net programming languages, is asynchronous in any case. 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. This forces me to construct while loops just after the call to copy that will execute until I can confirm the copy is complete.

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

While there are some circumstances where you'd want to avoid Task.Run, Task.Run(() => File.Move(source, dest) will work. It is worth considering because when a file is simply moved in the same disk/volume, it is an almost-instantaneous operation, as the headers are changed but the file contents are not moved. The various "pure" async methods invariably copy the stream, even when there is no need to do this, and as a result can be quite a bit slower in practice.

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

Note that this, copy file async, is still an open issue for .net development: https://github.com/dotnet/runtime/issues/20695