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;
46 ManualResetEvent handle;
48 internal static readonly CancellationTokenSource NoneSource = new CancellationTokenSource ();
49 internal static readonly CancellationTokenSource CanceledSource = new CancellationTokenSource ();
52 static readonly TimerCallback timer_callback;
56 static CancellationTokenSource ()
58 CanceledSource.canceled = true;
61 timer_callback = token => {
62 var cts = (CancellationTokenSource) token;
68 public CancellationTokenSource ()
70 callbacks = new ConcurrentDictionary<CancellationTokenRegistration, Action> ();
71 handle = new ManualResetEvent (false);
75 public CancellationTokenSource (int millisecondsDelay)
78 if (millisecondsDelay < -1)
79 throw new ArgumentOutOfRangeException ("millisecondsDelay");
81 if (millisecondsDelay != Timeout.Infinite)
82 timer = new Timer (timer_callback, this, millisecondsDelay, Timeout.Infinite);
85 public CancellationTokenSource (TimeSpan delay)
86 : this (CheckTimeout (delay))
91 public CancellationToken Token {
94 return new CancellationToken (this);
98 public bool IsCancellationRequested {
104 internal WaitHandle WaitHandle {
111 public void Cancel ()
116 // If parameter is true we throw exception as soon as they appear otherwise we aggregate them
117 public void Cancel (bool throwOnFirstException)
124 List<Exception> exceptions = null;
128 for (int id = int.MinValue + 1; id <= currId; id++) {
129 if (!callbacks.TryRemove (new CancellationTokenRegistration (id, this), out cb))
134 if (throwOnFirstException) {
139 } catch (Exception e) {
140 if (exceptions == null)
141 exceptions = new List<Exception> ();
151 if (exceptions != null)
152 throw new AggregateException (exceptions);
156 public void CancelAfter (TimeSpan delay)
158 CancelAfter (CheckTimeout (delay));
161 public void CancelAfter (int millisecondsDelay)
163 if (millisecondsDelay < -1)
164 throw new ArgumentOutOfRangeException ("millisecondsDelay");
168 if (canceled || millisecondsDelay == Timeout.Infinite)
172 // Have to be carefull not to create secondary background timer
173 var t = new Timer (timer_callback, this, Timeout.Infinite, Timeout.Infinite);
174 if (Interlocked.CompareExchange (ref timer, t, null) != null)
178 timer.Change (millisecondsDelay, Timeout.Infinite);
182 public static CancellationTokenSource CreateLinkedTokenSource (CancellationToken token1, CancellationToken token2)
184 return CreateLinkedTokenSource (new [] { token1, token2 });
187 public static CancellationTokenSource CreateLinkedTokenSource (params CancellationToken[] tokens)
190 throw new ArgumentNullException ("tokens");
192 if (tokens.Length == 0)
193 throw new ArgumentException ("Empty tokens array");
195 CancellationTokenSource src = new CancellationTokenSource ();
196 Action action = src.Cancel;
198 foreach (CancellationToken token in tokens) {
199 if (token.CanBeCanceled)
200 token.Register (action);
206 static int CheckTimeout (TimeSpan delay)
209 return checked ((int) delay.TotalMilliseconds);
210 } catch (OverflowException) {
211 throw new ArgumentOutOfRangeException ("delay");
215 void CheckDisposed ()
218 throw new ObjectDisposedException (GetType ().Name);
221 public void Dispose ()
229 void Dispose (bool disposing)
231 if (disposing && !disposed) {
233 Thread.MemoryBarrier ();
244 internal CancellationTokenRegistration Register (Action callback, bool useSynchronizationContext)
248 var tokenReg = new CancellationTokenRegistration (Interlocked.Increment (ref currId), this);
250 /* If the source is already canceled we execute the callback immediately
251 * if not, we try to add it to the queue and if it is currently being processed
252 * we try to execute it back ourselves to be sure the callback is ran
257 callbacks.TryAdd (tokenReg, callback);
258 if (canceled && callbacks.TryRemove (tokenReg, out callback))
265 internal void RemoveCallback (CancellationTokenRegistration reg)
267 // Ignore call if the source has been disposed
273 cbs.TryRemove (reg, out dummy);