//
// CancellationTokenSource.cs
//
-// Author:
+// Authors:
// Jérémie "Garuma" Laval <jeremie.laval@gmail.com>
+// Marek Safar (marek.safar@gmail.com)
//
// Copyright (c) 2009 Jérémie "Garuma" Laval
+// Copyright 2011 Xamarin, Inc (http://www.xamarin.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
-#if NET_4_0 || BOOTSTRAP_NET_4_0
-using System;
+#if NET_4_0
using System.Collections.Generic;
+using System.Collections.Concurrent;
namespace System.Threading
{
-
- public sealed class CancellationTokenSource : IDisposable
+#if !NET_4_5
+ sealed
+#endif
+ public class CancellationTokenSource : IDisposable
{
bool canceled;
- bool processed;
+ bool disposed;
int currId = int.MinValue;
+ ConcurrentDictionary<CancellationTokenRegistration, Action> callbacks;
+ CancellationTokenRegistration[] linkedTokens;
+
+ ManualResetEvent handle;
- Dictionary<CancellationTokenRegistration, Action> callbacks
- = new Dictionary<CancellationTokenRegistration, Action> ();
+ internal static readonly CancellationTokenSource NoneSource = new CancellationTokenSource ();
+ internal static readonly CancellationTokenSource CanceledSource = new CancellationTokenSource ();
- ManualResetEvent handle = new ManualResetEvent (false);
+#if NET_4_5
+ static readonly TimerCallback timer_callback;
+ Timer timer;
+#endif
+
+ static CancellationTokenSource ()
+ {
+ CanceledSource.canceled = true;
+
+#if NET_4_5
+ timer_callback = token => {
+ var cts = (CancellationTokenSource) token;
+ cts.CancelSafe ();
+ };
+#endif
+ }
+
+ public CancellationTokenSource ()
+ {
+ callbacks = new ConcurrentDictionary<CancellationTokenRegistration, Action> ();
+ handle = new ManualResetEvent (false);
+ }
+
+#if NET_4_5
+ public CancellationTokenSource (int millisecondsDelay)
+ : this ()
+ {
+ if (millisecondsDelay < -1)
+ throw new ArgumentOutOfRangeException ("millisecondsDelay");
+
+ if (millisecondsDelay != Timeout.Infinite)
+ timer = new Timer (timer_callback, this, millisecondsDelay, Timeout.Infinite);
+ }
+
+ public CancellationTokenSource (TimeSpan delay)
+ : this (CheckTimeout (delay))
+ {
+ }
+#endif
+
+ public CancellationToken Token {
+ get {
+ CheckDisposed ();
+ return new CancellationToken (this);
+ }
+ }
- object syncRoot = new object ();
+ public bool IsCancellationRequested {
+ get {
+ return canceled;
+ }
+ }
- internal static readonly CancellationTokenSource NoneSource = new CancellationTokenSource ();
+ internal WaitHandle WaitHandle {
+ get {
+ CheckDisposed ();
+ return handle;
+ }
+ }
public void Cancel ()
{
}
// If parameter is true we throw exception as soon as they appear otherwise we aggregate them
- public void Cancel (bool throwOnFirst)
+ public void Cancel (bool throwOnFirstException)
+ {
+ CheckDisposed ();
+ Cancellation (throwOnFirstException);
+ }
+
+ //
+ // Don't throw ObjectDisposedException if the callback
+ // is called concurrently with a Dispose
+ //
+ public void CancelSafe ()
+ {
+ if (!disposed)
+ Cancellation (true);
+ }
+
+ void Cancellation (bool throwOnFirstException)
{
+ if (canceled)
+ return;
+
+ Thread.MemoryBarrier ();
canceled = true;
- handle.Set ();
-
+
+ Thread.MemoryBarrier ();
+
+ // Dispose might be running at same time
+ if (!disposed)
+ handle.Set ();
+
+ if (linkedTokens != null)
+ UnregisterLinkedTokens ();
+
+ var cbs = callbacks;
+ if (cbs == null)
+ return;
+
List<Exception> exceptions = null;
- if (!throwOnFirst)
- exceptions = new List<Exception> ();
-
- lock (callbacks) {
- foreach (KeyValuePair<CancellationTokenRegistration, Action> item in callbacks) {
- if (throwOnFirst) {
- item.Value ();
+
+ try {
+ Action cb;
+ for (int id = currId; id != int.MinValue; id--) {
+ if (!cbs.TryRemove (new CancellationTokenRegistration (id, this), out cb))
+ continue;
+ if (cb == null)
+ continue;
+
+ if (throwOnFirstException) {
+ cb ();
} else {
try {
- item.Value ();
+ cb ();
} catch (Exception e) {
+ if (exceptions == null)
+ exceptions = new List<Exception> ();
+
exceptions.Add (e);
}
}
}
+ } finally {
+ cbs.Clear ();
}
-
- Thread.MemoryBarrier ();
- processed = true;
-
- if (exceptions != null && exceptions.Count > 0)
+
+ if (exceptions != null)
throw new AggregateException (exceptions);
}
-
- public void Dispose ()
+
+#if NET_4_5
+ public void CancelAfter (TimeSpan delay)
{
-
+ CancelAfter (CheckTimeout (delay));
}
-
+
+ public void CancelAfter (int millisecondsDelay)
+ {
+ if (millisecondsDelay < -1)
+ throw new ArgumentOutOfRangeException ("millisecondsDelay");
+
+ CheckDisposed ();
+
+ if (canceled || millisecondsDelay == Timeout.Infinite)
+ return;
+
+ if (timer == null) {
+ // Have to be carefull not to create secondary background timer
+ var t = new Timer (timer_callback, this, Timeout.Infinite, Timeout.Infinite);
+ if (Interlocked.CompareExchange (ref timer, t, null) != null)
+ t.Dispose ();
+ }
+
+ timer.Change (millisecondsDelay, Timeout.Infinite);
+ }
+#endif
+
public static CancellationTokenSource CreateLinkedTokenSource (CancellationToken token1, CancellationToken token2)
{
- return CreateLinkedTokenSource (new CancellationToken[] { token1, token2 });
+ return CreateLinkedTokenSource (new [] { token1, token2 });
}
public static CancellationTokenSource CreateLinkedTokenSource (params CancellationToken[] tokens)
{
+ if (tokens == null)
+ throw new ArgumentNullException ("tokens");
+
+ if (tokens.Length == 0)
+ throw new ArgumentException ("Empty tokens array");
+
CancellationTokenSource src = new CancellationTokenSource ();
- Action action = src.Cancel;
-
- foreach (CancellationToken token in tokens)
- token.Register (action);
+ Action action = src.CancelSafe;
+ var registrations = new List<CancellationTokenRegistration> (tokens.Length);
+
+ foreach (CancellationToken token in tokens) {
+ if (token.CanBeCanceled)
+ registrations.Add (token.Register (action));
+ }
+ src.linkedTokens = registrations.ToArray ();
return src;
}
-
- public CancellationToken Token {
- get {
- return CreateToken ();
+
+ static int CheckTimeout (TimeSpan delay)
+ {
+ try {
+ return checked ((int) delay.TotalMilliseconds);
+ } catch (OverflowException) {
+ throw new ArgumentOutOfRangeException ("delay");
}
}
-
- public bool IsCancellationRequested {
- get {
- return canceled;
- }
+
+ void CheckDisposed ()
+ {
+ if (disposed)
+ throw new ObjectDisposedException (GetType ().Name);
}
-
- internal WaitHandle WaitHandle {
- get {
- return handle;
+
+ public void Dispose ()
+ {
+ Dispose (true);
+ }
+
+#if NET_4_5
+ protected virtual
+#endif
+ void Dispose (bool disposing)
+ {
+ if (disposing && !disposed) {
+ disposed = true;
+ Thread.MemoryBarrier ();
+
+ if (!canceled) {
+ UnregisterLinkedTokens ();
+ callbacks = null;
+ } else {
+ handle.WaitOne ();
+ }
+#if NET_4_5
+ if (timer != null)
+ timer.Dispose ();
+#endif
+
+ handle.Dispose ();
}
}
+
+ void UnregisterLinkedTokens ()
+ {
+ var registrations = Interlocked.Exchange (ref linkedTokens, null);
+ if (registrations == null)
+ return;
+ foreach (var linked in registrations)
+ linked.Dispose ();
+ }
internal CancellationTokenRegistration Register (Action callback, bool useSynchronizationContext)
{
- CancellationTokenRegistration tokenReg = GetTokenReg ();
- if (canceled) {
+ CheckDisposed ();
+
+ var tokenReg = new CancellationTokenRegistration (Interlocked.Increment (ref currId), this);
+
+ /* If the source is already canceled we execute the callback immediately
+ * if not, we try to add it to the queue and if it is currently being processed
+ * we try to execute it back ourselves to be sure the callback is ran
+ */
+ if (canceled)
callback ();
- } else {
- bool temp = false;
- lock (syncRoot) {
- if (!(temp = canceled))
- callbacks.Add (tokenReg, callback);
- }
- if (temp)
+ else {
+ callbacks.TryAdd (tokenReg, callback);
+ if (canceled && callbacks.TryRemove (tokenReg, out callback))
callback ();
}
return tokenReg;
}
-
- internal void RemoveCallback (CancellationTokenRegistration tokenReg)
- {
- if (!canceled) {
- lock (syncRoot) {
- if (!canceled) {
- callbacks.Remove (tokenReg);
- return;
- }
- }
- }
-
- SpinWait sw = new SpinWait ();
- while (!processed)
- sw.SpinOnce ();
-
- }
- CancellationTokenRegistration GetTokenReg ()
+ internal void RemoveCallback (CancellationTokenRegistration reg)
{
- CancellationTokenRegistration registration
- = new CancellationTokenRegistration (Interlocked.Increment (ref currId), this);
-
- return registration;
- }
-
- CancellationToken CreateToken ()
- {
- CancellationToken tk = new CancellationToken (canceled);
- tk.Source = this;
-
- return tk;
+ // Ignore call if the source has been disposed
+ if (disposed)
+ return;
+ Action dummy;
+ var cbs = callbacks;
+ if (cbs != null)
+ cbs.TryRemove (reg, out dummy);
}
}
}