X-Git-Url: http://wien.tomnetworks.com/gitweb/?a=blobdiff_plain;f=mcs%2Fclass%2Fcorlib%2FSystem.Threading%2FCancellationTokenSource.cs;h=c5557f0446e69b7808e73a295b08c7c244d4e469;hb=424595ea7043f30b0553f2842654db13baeda3ca;hp=05ca53a182f1f85e389adf6278282b35e0ff1e0c;hpb=292eea651d0ee9a04a0da41165aef38fbdbc5c6b;p=mono.git diff --git a/mcs/class/corlib/System.Threading/CancellationTokenSource.cs b/mcs/class/corlib/System.Threading/CancellationTokenSource.cs index 05ca53a182f..c5557f0446e 100644 --- a/mcs/class/corlib/System.Threading/CancellationTokenSource.cs +++ b/mcs/class/corlib/System.Threading/CancellationTokenSource.cs @@ -1,10 +1,12 @@ // // CancellationTokenSource.cs // -// Author: +// Authors: // Jérémie "Garuma" Laval +// 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 @@ -24,28 +26,88 @@ // 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 callbacks; + CancellationTokenRegistration[] linkedTokens; + + ManualResetEvent handle; - Dictionary callbacks - = new Dictionary (); + 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 (); + 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 () { @@ -53,124 +115,210 @@ namespace System.Threading } // 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 exceptions = null; - if (!throwOnFirst) - exceptions = new List (); - - lock (callbacks) { - foreach (KeyValuePair 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 (); + 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 (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); } } }