我正在创建一个将远程网络文件复制到本地磁盘的应用程序,整个操作包含File.Copy(remotePath, localPath)
个调用。有时复制操作会挂起或运行得更慢,但是不会抛出任何异常,但是我想要阻止它,并且无法从取消令牌中检查IsCancellationRequested
,就像这里的许多帖子推荐用于操作是可分的。在这种情况下我该如何取消任务?
答案 0 :(得分:1)
令人遗憾的是,.NET没有管理方式取消文件复制操作,据我所知,它只能通过本机调用来完成。
您需要进行的本机调用是FileCopyEx它有一个参数和一个允许取消的回调函数。
以下是如何在.NET中执行此操作:
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CopyFileEx(string lpExistingFileName, string lpNewFileName,
CopyProgressRoutine lpProgressRoutine, IntPtr lpData, ref bool pbCancel,
CopyFileFlags dwCopyFlags);
private const int CopyCanceledErrorCode = 1235;
private static void Copy(string oldFile, string newFile, CancellationToken cancellationToken)
{
var cancel = false;
CopyProgressRoutine callback =
(size, transferred, streamSize, bytesTransferred, number, reason, file, destinationFile, data) =>
{
if (cancellationToken.IsCancellationRequested)
return CopyProgressResult.PROGRESS_CANCEL;
else
return CopyProgressResult.PROGRESS_CONTINUE;
};
if (!CopyFileEx(oldFile, newFile, callback, IntPtr.Zero, ref cancel, CopyFileFlags.COPY_FILE_RESTARTABLE))
{
var error = Marshal.GetLastWin32Error();
if (error == CopyCanceledErrorCode)
throw new OperationCanceledException(cancellationToken);
//Throws the more .NET friendly OperationCanceledException
throw new Win32Exception(error);
}
//This prevents the callback delegate from getting GC'ed before the native call is done with it.
GC.KeepAlive(callback);
}
有一点需要注意,我的第一次尝试是
private static void Copy(string oldFile, string newFile, CancellationToken cancellationToken)
{
bool cancel = false;
using (cancellationToken.Register(() => cancel = true))
{
if (!CopyFileEx(oldFile, newFile, null, IntPtr.Zero, ref cancel, CopyFileFlags.COPY_FILE_RESTARTABLE))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
GC.KeepAlive(cancel);
}
}
但这并没有导致副本取消。我不知道是不是因为你不能从委托中更改bool,或者是因为CopyFileEx不经常检查bool是否发生变化。使用取消副本的回调方法更加可靠。
APPENDEX:
以下是所有枚举和代表的副本,因此您无需像我一样在Pinvoke.net上搜索它们。
enum CopyProgressResult : uint
{
PROGRESS_CONTINUE = 0,
PROGRESS_CANCEL = 1,
PROGRESS_STOP = 2,
PROGRESS_QUIET = 3
}
enum CopyProgressCallbackReason : uint
{
CALLBACK_CHUNK_FINISHED = 0x00000000,
CALLBACK_STREAM_SWITCH = 0x00000001
}
[Flags]
enum CopyFileFlags : uint
{
COPY_FILE_FAIL_IF_EXISTS = 0x00000001,
COPY_FILE_RESTARTABLE = 0x00000002,
COPY_FILE_OPEN_SOURCE_FOR_WRITE = 0x00000004,
COPY_FILE_ALLOW_DECRYPTED_DESTINATION = 0x00000008,
COPY_FILE_COPY_SYMLINK = 0x00000800 //NT 6.0+
}
delegate CopyProgressResult CopyProgressRoutine(
long TotalFileSize,
long TotalBytesTransferred,
long StreamSize,
long StreamBytesTransferred,
uint dwStreamNumber,
CopyProgressCallbackReason dwCallbackReason,
IntPtr hSourceFile,
IntPtr hDestinationFile,
IntPtr lpData);
答案 1 :(得分:1)
将这两个文件打开一个FileStream并以块的形式复制它。 如果你能使用异步方法,请使用。
简单的形式是读取数据以缓冲和写入 缓冲出来。扩展版本用于读取一个缓冲区 并同时从第二个缓冲区写入。
在每次读/写调用之后或之前,操作是 取消。
如果出现问题,你会在某个时间点得到一个时间 取决于流的ReadTimeout WriteTimeout属性。
Windows默认缓冲区大小为4096字节。 以下是扩展方法:
public static void WriteTo(this Stream source, Stream distiantion, int bufferSize = PNetIO.DefaultBufferSize, CancellationToken cancellationToken = default(CancellationToken))
{
var buffer = new byte[bufferSize];
int c;
while ((c = source.Read(buffer, 0, bufferSize)) > 0)
{
distiantion.Write(buffer, 0, c);
cancellationToken.ThrowIfCancellationRequested();
}
}
public static async Task WriteToAsync(this Stream source, Stream distiantion, int bufferSize = PNetIO.DefaultBufferSize, CancellationToken cancellationToken = default(CancellationToken))
{
if (!source.CanRead)
return;
var buffer = new Byte[bufferSize * 2];
var c = await source.ReadAsync(buffer, bufferSize, bufferSize, cancellationToken).ConfigureAwait(false);
if (c <= 0)
return;
var w = distiantion.WriteAsync(buffer, bufferSize, c, cancellationToken);
while ((c = await source.ReadAsync(buffer, 0, bufferSize).ConfigureAwait(false)) > 0)
{
await w.ConfigureAwait(false);
w = distiantion.WriteAsync(buffer, 0, c, cancellationToken);
if ((c = await source.ReadAsync(buffer, bufferSize, bufferSize, cancellationToken).ConfigureAwait(false)) <= 0)
break;
await w.ConfigureAwait(false);
w = distiantion.WriteAsync(buffer, bufferSize, c, cancellationToken);
}
await w.ConfigureAwait(false);
await distiantion.FlushAsync(cancellationToken).ConfigureAwait(false);
}
你可以像这样使用它:
var cancel = new CancellationTokenSource();
using (var source = File.OpenRead("source"))
using (var dest = File.OpenWrite("dest"))
await source.WriteToAsync(dest, cancellationToken: cancel.Token);