这是一个阻塞调用者线程但不支持取消的方法的(愚蠢的)示例:
Public Sub WorkUntil5()
Threading.SpinWait.SpinUntil(Function() Now.Hour >= 17)
End Sub
在最坏的情况下,调用此方法需要 17 个小时才能返回。假设我无权访问此方法的源代码。 如何将调用包装在采用 CancellationToken 的方法中?
目标是让 WorkUntil5()
运行直到请求取消。此时应使用任何可能的方式终止通话。
这是我自己想出的最佳方法。它使用任务,但它仍然阻塞调用者的线程。感觉有些不对劲。认为应该有更好的方法在第一次调用返回后调用 mres.Set()
。
Public Sub WorkUntilYouGetBored(cancellationToken As Threading.CancellationToken)
Dim mres As New Threading.ManualResetEventSlim
Using cancellationToken.Register(Sub() mres.Set())
Dim t = Task.Factory.StartNew(Sub() WorkUntil5())
t.ContinueWith(Sub() mres.Set())
mres.Wait()
End Using
If cancellationToken.IsCancellationRequested Then
Console.WriteLine("You went home early.")
Else
Console.WriteLine("It's time to go home.")
End If
End Sub
最佳答案
让我们改一下问题。您有一个名为 WorkUntil5 的不可取消操作,您希望将其取消。你怎么做到的?
Stephen Toub 在“How do I cancel non-cancelable async operations?”中讨论了这种情况。
唯一真正的解决方案是修复 WorkUntil5 以允许取消。如果那不可能(为什么?),您必须决定“取消”是什么意思?
你想:
- 真的要取消长操作,还是要
- 停止等待?
这两种操作都是不可靠的,因为您无法知道长时间运行的方法中还有哪些未完成。这不是 TPL 设计的问题,而是长时间运行方法的设计问题。
操作 #1 实际上是不可能的,因为该方法没有提供任何方法来取消它。
操作 #2 可以使用 Tasks 和 TaskCompletionSource 来处理,但不是很可靠,因为您无法知道长时间的操作是否遗留垃圾或异常终止。
Stephen Toub 展示了如何做到这一点,甚至提供了一个扩展方法来做到这一点:
public static async Task<T> WithCancellation<T>(
this Task<T> task, CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<bool>();
using(cancellationToken.Register(
s => ((TaskCompletionSource<bool>)s).TrySetResult(true), tcs))
if (task != await Task.WhenAny(task, tcs.Task))
throw new OperationCanceledException(cancellationToken);
return await task;
}
关于.net - 如果无法轮询 CancellationToken,则提供取消,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13794229/