如果无法轮询CancellationToken,则提供取消

时间:2012-12-10 02:36:30

标签: .net vb.net task-parallel-library cancellation cancellation-token

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

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

1 个答案:

答案 0 :(得分:2)

让我们重新解释一下这个问题。您有一个名为WorkUntil5的不可取消操作要取消。你怎么能这样做?

Stephen Toub在“How do I cancel non-cancelable async operations?”中讨论了这个场景。

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

你想:

  1. 真的取消长时间操作,或者你想要
  2. 停止等待吗?
  3. 这两个操作都不可靠,因为你无法知道长时间运行方法中未完成的内容。这不是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; 
    }