|
//------------------------------------------------------------------------------
// <copyright file="CancellationTokenHelper.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace System.Web.Util {
using System;
using System.Threading;
// Helper class for dealing with CancellationToken instances. Our Cancel and Dispose methods
// are fully thread-safe and will never block or throw, while a normal CancellationTokenSource
// doesn't make these guarantees.
internal sealed class CancellationTokenHelper : IDisposable {
private const int STATE_CREATED = 0;
private const int STATE_CANCELING = 1;
private const int STATE_CANCELED = 2;
private const int STATE_DISPOSING = 3;
private const int STATE_DISPOSED = 4; // terminal state
// A CancellationTokenHelper which is already marked as disposed; useful for avoiding
// allocations of CancellationTokenHelper instances which are never observed.
internal static readonly CancellationTokenHelper StaticDisposed = GetStaticDisposedHelper();
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
private int _state;
public CancellationTokenHelper(bool canceled) {
if (canceled) {
_cts.Cancel();
}
_state = (canceled) ? STATE_CANCELED : STATE_CREATED;
}
internal bool IsCancellationRequested {
get { return _cts.IsCancellationRequested; }
}
internal CancellationToken Token {
get { return _cts.Token; }
}
// Cancels the token.
public void Cancel() {
if (Interlocked.CompareExchange(ref _state, STATE_CANCELING, STATE_CREATED) == STATE_CREATED) {
// Only allow cancellation if the token hasn't yet been canceled or disposed.
// Cancel on a ThreadPool thread so that we can release the original thread back to IIS.
// We can use UnsafeQUWI to avoid an extra ExecutionContext capture since CancellationToken already captures it.
ThreadPool.UnsafeQueueUserWorkItem(_ => {
try {
_cts.Cancel();
}
catch {
// ---- all exceptions to avoid killing the worker process.
}
finally {
if (Interlocked.CompareExchange(ref _state, STATE_CANCELED, STATE_CANCELING) == STATE_DISPOSING) {
// A call to Dispose() came in on another thread while we were in the middle of a cancel
// operation. That thread will no-op, so we'll dispose of it here.
_cts.Dispose();
Interlocked.Exchange(ref _state, STATE_DISPOSED);
}
}
}, null);
}
}
// Disposes of the token.
public void Dispose() {
// Only allow a single call to Dispose.
int originalState = Interlocked.Exchange(ref _state, STATE_DISPOSING);
switch (originalState) {
case STATE_CREATED:
case STATE_CANCELED:
// If Cancel() hasn't yet been called or has already run to completion,
// the underlying CTS guarantees that the Dispose method won't block
// or throw, so we can just call it directly.
_cts.Dispose();
Interlocked.Exchange(ref _state, STATE_DISPOSED);
break;
case STATE_DISPOSED:
// If the object was already disposed, we need to reset the flag here
// since we accidentally blew it away with the original Exchange.
Interlocked.Exchange(ref _state, STATE_DISPOSED);
break;
// Otherwise, the object is already canceling or disposing, so the
// other thread will handle the call to Dispose().
}
}
private static CancellationTokenHelper GetStaticDisposedHelper() {
CancellationTokenHelper helper = new CancellationTokenHelper(false);
helper.Dispose();
return helper;
}
}
}
|