3

这是一个阻塞调用者线程但不支持取消的方法的(愚蠢的)示例:

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
4

1 回答 1

3

让我们重新表述这个问题。您有一个名为 WorkUntil5 的不可取消操作,您想取消该操作。你怎么能那样做?

Stephen Toub 在“如何取消不可取消的异步操作? ”中讨论了这种情况。

唯一真正的解决方案是修复 WorkUntil5 以便它允许取消。如果那不可能(为什么?),您必须确定“取消”是什么意思?

你想要_____吗:

  1. 真的取消长操作,还是要
  2. 别再等了?

这两种操作都不可靠,因为您无法知道在长时间运行的方法中还有什么未完成的。这不是 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; 
}
于 2012-12-10T09:40:04.697 回答