1 //------------------------------------------------------------------------------
2 // <copyright file="CancellationTokenHelper.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 //------------------------------------------------------------------------------
7 namespace System.Web.Util {
9 using System.Threading;
11 // Helper class for dealing with CancellationToken instances. Our Cancel and Dispose methods
12 // are fully thread-safe and will never block or throw, while a normal CancellationTokenSource
13 // doesn't make these guarantees.
15 internal sealed class CancellationTokenHelper : IDisposable {
17 private const int STATE_CREATED = 0;
18 private const int STATE_CANCELING = 1;
19 private const int STATE_CANCELED = 2;
20 private const int STATE_DISPOSING = 3;
21 private const int STATE_DISPOSED = 4; // terminal state
23 // A CancellationTokenHelper which is already marked as disposed; useful for avoiding
24 // allocations of CancellationTokenHelper instances which are never observed.
25 internal static readonly CancellationTokenHelper StaticDisposed = GetStaticDisposedHelper();
27 private readonly CancellationTokenSource _cts = new CancellationTokenSource();
30 public CancellationTokenHelper(bool canceled) {
34 _state = (canceled) ? STATE_CANCELED : STATE_CREATED;
37 internal bool IsCancellationRequested {
38 get { return _cts.IsCancellationRequested; }
41 internal CancellationToken Token {
42 get { return _cts.Token; }
46 public void Cancel() {
47 if (Interlocked.CompareExchange(ref _state, STATE_CANCELING, STATE_CREATED) == STATE_CREATED) {
48 // Only allow cancellation if the token hasn't yet been canceled or disposed.
49 // Cancel on a ThreadPool thread so that we can release the original thread back to IIS.
50 // We can use UnsafeQUWI to avoid an extra ExecutionContext capture since CancellationToken already captures it.
51 ThreadPool.UnsafeQueueUserWorkItem(_ => {
56 // ---- all exceptions to avoid killing the worker process.
59 if (Interlocked.CompareExchange(ref _state, STATE_CANCELED, STATE_CANCELING) == STATE_DISPOSING) {
60 // A call to Dispose() came in on another thread while we were in the middle of a cancel
61 // operation. That thread will no-op, so we'll dispose of it here.
63 Interlocked.Exchange(ref _state, STATE_DISPOSED);
70 // Disposes of the token.
71 public void Dispose() {
72 // Only allow a single call to Dispose.
73 int originalState = Interlocked.Exchange(ref _state, STATE_DISPOSING);
74 switch (originalState) {
77 // If Cancel() hasn't yet been called or has already run to completion,
78 // the underlying CTS guarantees that the Dispose method won't block
79 // or throw, so we can just call it directly.
81 Interlocked.Exchange(ref _state, STATE_DISPOSED);
85 // If the object was already disposed, we need to reset the flag here
86 // since we accidentally blew it away with the original Exchange.
87 Interlocked.Exchange(ref _state, STATE_DISPOSED);
90 // Otherwise, the object is already canceling or disposing, so the
91 // other thread will handle the call to Dispose().
95 private static CancellationTokenHelper GetStaticDisposedHelper() {
96 CancellationTokenHelper helper = new CancellationTokenHelper(false);