Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.0k views
in Technique[技术] by (71.8m points)

.net - Providing cancellation if polling CancellationToken is not possible

Here's a (silly) example of a method that blocks the caller's thread but does not support cancellation:

Public Sub WorkUntil5()
    Threading.SpinWait.SpinUntil(Function() Now.Hour >= 17)
End Sub

In the worst case scenario, calling this method takes 17 hours to return. Pretend that I don't have access to the source code of this method. How do I wrap the call in a method that takes a CancellationToken?

The goal is to let WorkUntil5() run until cancellation is requested. At that point the call should be terminated using any means possible.

Here's the best way I could come up with myself. It uses tasks, but it still blocks the caller's thread. Something about that doesn't feel right. Thinking there should be a better way to call mres.Set() once the first call returns.

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
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Let's rephrase the question. You have a non-cancelable operation called WorkUntil5 which you want to cancel. How can you do that?

Stephen Toub discusses this scenario in "How do I cancel non-cancelable async operations?".

The only real solution is to fix WorkUntil5 so that it allows cancellation. If that isn't possible (why?), you have to decide what you mean by "cancellation"?

Do you want to:

  1. Really cancel the long operation, or do you want to
  2. Stop waiting for it?

Both operations are unreliable because you have no way to know what was left unfinished in the long-running method. It's not a problem with the design of the TPL but the design of the long-running method.

Operation #1 isn't really possible, since the method doesn't provide any way to cancel it.

Operation #2 can be handled using Tasks and the TaskCompletionSource but isn't very reliable, because you have no way of knowing if the long operation left garbage behind or terminated with an exception.

Stephen Toub shows how to do this and even provides an extension method to do that:

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; 
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...