[英]Aborting/canceling tasks that consist of a single unbreakable operation
我正在制作一个将远程网络文件复制到本地磁盘的应用程序,整个操作由File.Copy(remotePath, localPath)
调用组成。 有时复制操作会挂起或运行得特别慢,但是还没有引发任何异常,但是我想停止它,而且无法从取消令牌中检查IsCancellationRequested
,就像这里的许多帖子都建议将操作整除的情况一样。 在这种情况下,我该怎么做才能取消任务?
不幸的是,.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);
}
}
但这并没有导致副本被取消。 我不知道这仅仅是因为您不能从委托更改布尔值还是因为CopyFileEx没有频繁检查布尔值是否发生变化。 使用取消副本的回调方法更加可靠。
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);
打开两个文件的FileStream并将其分块复制。 如果您可以使用异步方法,请使用。
简单的形式是读取要缓冲的数据并写出缓冲。 扩展的版本将读入一个缓冲区,并同时从第二个缓冲区写入。
在每个读/写调用之后或之前,该操作是可取消的。
如果存在问题,则根据流的ReadTimeout WriteTimeout属性,您将在某个时刻获得timeoutexception。
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);
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.