Why CancellationTokenRegistration exists and why does it implement IDisposable

前端 未结 2 1269
礼貌的吻别
礼貌的吻别 2020-12-28 16:45

I\'ve been seeing code that uses Cancellation.Register with a using clause on the CancellationTokenRegistration result:



        
2条回答
  •  青春惊慌失措
    2020-12-28 17:06

    This pattern is a convenient way to make sure CancellationTokenRegistration.Unregister() is called automatically. It's often used by Stephen Toub in his Parallel Programming with .NET blog posts, e.g. here.

    I get that you should make sure you Dispose an IDisposable, but why does it even implements IDisposable? what resources does it have to release? The only methods it has regard equality.

    IMO, the best answer to this can be found in the .NET 4 Cancellation Framework post by Microsoft's Mike Liddell:

    When a callback is registered to a CancellationToken, the current thread's ExecutionContext is captured so that the callback will be run with the the exact same security context . The capturing of the current thread's synchronization context is optional can be requested via an overload of ct.Register() if required. Callbacks are normally stored and then run when cancellation is requested, but if a callback is registered after cancellation has been requested, the callback will run immediately on the current thread, or via Send() on the current SynchronizationContext if applicable.

    When a callback is registered to a CancellationToken, the returned object is a CancellationTokenRegistration. This is a light struct type that is IDiposable, and disposing this registration object causes the callback to be deregistered. A guarantee is made that after the Dispose() method has returned, the registered callback is neither running nor will subsequently commence. A consequence of this is that CancellationTokenRegistration.Dispose() must block if the callback is currently executing. Hence, all registered callbacks should be fast and not block for any significant duration.

    Another relevant document by Mike Liddell is "Using Cancellation Support in .NET Framework 4" (UsingCancellationinNET4.pdf).

    Updated, this is verifiable here in the Reference Source.

    It's also important to note, the cancellation callback is registered with the CancellationTokenSource, not with CancellationToken. So, if CancellationTokenRegistration.Dispose() is not correctly scoped, the registration will remain active for the lifetime of the parent CancellationTokenSource object. This may lead to an unexpected callback when the scope of the async operation is over, e.g.:

    async Task TestAsync(WebClient wc, CancellationToken token)
    {
        token.Register(() => wc.CancelAsync());
        await wc.DownloadStringAsync(new Uri("http://www.hamster.com"));
    }
    
    // CancellationTokenSource.Cancel() may still get called later,
    // in which case wc.CancelAsync() will be invoked too
    

    Thus, is important to scope the disposable CancellationTokenRegistration with using (or call CancellationTokenRegistration.Dispose() explicitly with try/finally).

提交回复
热议问题