2 // CancellationTokenSource.cs
5 // Jérémie "Garuma" Laval <jeremie.laval@gmail.com>
6 // Marek Safar (marek.safar@gmail.com)
8 // Copyright (c) 2009 Jérémie "Garuma" Laval
9 // Copyright 2011 Xamarin, Inc (http://www.xamarin.com)
11 // Permission is hereby granted, free of charge, to any person obtaining a copy
12 // of this software and associated documentation files (the "Software"), to deal
13 // in the Software without restriction, including without limitation the rights
14 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15 // copies of the Software, and to permit persons to whom the Software is
16 // furnished to do so, subject to the following conditions:
18 // The above copyright notice and this permission notice shall be included in
19 // all copies or substantial portions of the Software.
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
30 using System.Collections.Generic;
31 using System.Collections.Concurrent;
33 namespace System.Threading
38 public class CancellationTokenSource : IDisposable
43 int currId = int.MinValue;
44 ConcurrentDictionary<CancellationTokenRegistration, Action> callbacks;
45 CancellationTokenRegistration[] linkedTokens;
47 ManualResetEvent handle;
49 internal static readonly CancellationTokenSource NoneSource = new CancellationTokenSource ();
50 internal static readonly CancellationTokenSource CanceledSource = new CancellationTokenSource ();
53 static readonly TimerCallback timer_callback;
57 static CancellationTokenSource ()
59 CanceledSource.canceled = true;
62 timer_callback = token => {
63 var cts = (CancellationTokenSource) token;
69 public CancellationTokenSource ()
71 callbacks = new ConcurrentDictionary<CancellationTokenRegistration, Action> ();
72 handle = new ManualResetEvent (false);
76 public CancellationTokenSource (int millisecondsDelay)
79 if (millisecondsDelay < -1)
80 throw new ArgumentOutOfRangeException ("millisecondsDelay");
82 if (millisecondsDelay != Timeout.Infinite)
83 timer = new Timer (timer_callback, this, millisecondsDelay, Timeout.Infinite);
86 public CancellationTokenSource (TimeSpan delay)
87 : this (CheckTimeout (delay))
92 public CancellationToken Token {
95 return new CancellationToken (this);
99 public bool IsCancellationRequested {
105 internal WaitHandle WaitHandle {
112 public void Cancel ()
117 // If parameter is true we throw exception as soon as they appear otherwise we aggregate them
118 public void Cancel (bool throwOnFirstException)
125 Thread.MemoryBarrier ();
129 if (linkedTokens != null)
130 UnregisterLinkedTokens ();
132 List<Exception> exceptions = null;
136 for (int id = int.MinValue + 1; id <= currId; id++) {
137 if (!callbacks.TryRemove (new CancellationTokenRegistration (id, this), out cb))
142 if (throwOnFirstException) {
147 } catch (Exception e) {
148 if (exceptions == null)
149 exceptions = new List<Exception> ();
159 if (exceptions != null)
160 throw new AggregateException (exceptions);
163 /* This is the callback registered on linked tokens
164 * so that they don't throw an ODE if the callback
165 * is called concurrently with a Dispose
167 void SafeLinkedCancel ()
171 } catch (ObjectDisposedException) {}
175 public void CancelAfter (TimeSpan delay)
177 CancelAfter (CheckTimeout (delay));
180 public void CancelAfter (int millisecondsDelay)
182 if (millisecondsDelay < -1)
183 throw new ArgumentOutOfRangeException ("millisecondsDelay");
187 if (canceled || millisecondsDelay == Timeout.Infinite)
191 // Have to be carefull not to create secondary background timer
192 var t = new Timer (timer_callback, this, Timeout.Infinite, Timeout.Infinite);
193 if (Interlocked.CompareExchange (ref timer, t, null) != null)
197 timer.Change (millisecondsDelay, Timeout.Infinite);
201 public static CancellationTokenSource CreateLinkedTokenSource (CancellationToken token1, CancellationToken token2)
203 return CreateLinkedTokenSource (new [] { token1, token2 });
206 public static CancellationTokenSource CreateLinkedTokenSource (params CancellationToken[] tokens)
209 throw new ArgumentNullException ("tokens");
211 if (tokens.Length == 0)
212 throw new ArgumentException ("Empty tokens array");
214 CancellationTokenSource src = new CancellationTokenSource ();
215 Action action = src.SafeLinkedCancel;
216 var registrations = new List<CancellationTokenRegistration> (tokens.Length);
218 foreach (CancellationToken token in tokens) {
219 if (token.CanBeCanceled)
220 registrations.Add (token.Register (action));
222 src.linkedTokens = registrations.ToArray ();
227 static int CheckTimeout (TimeSpan delay)
230 return checked ((int) delay.TotalMilliseconds);
231 } catch (OverflowException) {
232 throw new ArgumentOutOfRangeException ("delay");
236 void CheckDisposed ()
239 throw new ObjectDisposedException (GetType ().Name);
242 public void Dispose ()
250 void Dispose (bool disposing)
252 if (disposing && !disposed) {
253 Thread.MemoryBarrier ();
257 Thread.MemoryBarrier ();
258 UnregisterLinkedTokens ();
269 void UnregisterLinkedTokens ()
271 var registrations = Interlocked.Exchange (ref linkedTokens, null);
272 if (registrations == null)
274 foreach (var linked in registrations)
278 internal CancellationTokenRegistration Register (Action callback, bool useSynchronizationContext)
282 var tokenReg = new CancellationTokenRegistration (Interlocked.Increment (ref currId), this);
284 /* If the source is already canceled we execute the callback immediately
285 * if not, we try to add it to the queue and if it is currently being processed
286 * we try to execute it back ourselves to be sure the callback is ran
291 callbacks.TryAdd (tokenReg, callback);
292 if (canceled && callbacks.TryRemove (tokenReg, out callback))
299 internal void RemoveCallback (CancellationTokenRegistration reg)
301 // Ignore call if the source has been disposed
307 cbs.TryRemove (reg, out dummy);