using System; using System.Threading; using Implab.Parallels; namespace Implab { public class CancellationToken : ICancellationToken { const int CANCEL_NOT_REQUESTED = 0; const int CANCEL_REQUESTING = 1; const int CANCEL_REQUESTED = 2; volatile int m_state = CANCEL_NOT_REQUESTED; Action m_handler; Parallels.SimpleAsyncQueue> m_handlers; public bool IsCancellationRequested { get { return m_state == CANCEL_REQUESTED; } } public Exception CancellationReason { get; set; } public void CancellationRequested(Action handler) { Safe.ArgumentNotNull(handler, nameof(handler)); if (IsCancellationRequested) { handler(CancellationReason); } else { EnqueueHandler(handler); if (IsCancellationRequested && TryDequeueHandler(out handler)) handler(CancellationReason); } } bool TryDequeueHandler(out Action handler) { handler = Interlocked.Exchange(ref m_handler, null); if (handler != null) return true; else if (m_handlers != null) return m_handlers.TryDequeue(out handler); else return false; } void EnqueueHandler(Action handler) { if (Interlocked.CompareExchange(ref m_handler, handler, null) != null) { if (m_handlers == null) // compare-exchange will fprotect from loosing already created queue Interlocked.CompareExchange(ref m_handlers, new SimpleAsyncQueue>(), null); m_handlers.Enqueue(handler); } } void RequestCancellation(Exception reason) { if (Interlocked.CompareExchange(ref m_state, CANCEL_REQUESTING, CANCEL_NOT_REQUESTED) == CANCEL_NOT_REQUESTED) { if (reason == null) reason = new OperationCanceledException(); CancellationReason = reason; m_state = CANCEL_REQUESTED; } } } }