这是阻止调用者线程但不支持取消的方法的一个(愚蠢)示例:
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
答案 0 :(得分:2)
让我们重新解释一下这个问题。您有一个名为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;
}