CancellationTokenSource.cs
Go to the documentation of this file.
1 //
2 // CancellationTokenSource.cs
3 //
4 // Authors:
5 // Jérémie "Garuma" Laval <jeremie.laval@gmail.com>
6 // Marek Safar (marek.safar@gmail.com)
7 //
8 // Copyright (c) 2009 Jérémie "Garuma" Laval
9 // Copyright 2011 Xamarin, Inc (http://www.xamarin.com)
10 //
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:
17 //
18 // The above copyright notice and this permission notice shall be included in
19 // all copies or substantial portions of the Software.
20 //
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
27 // THE SOFTWARE.
28 
29 #if NET_4_0
32 
33 namespace System.Threading
34 {
35 #if !NET_4_5
36  sealed
37 #endif
38  public class CancellationTokenSource : IDisposable
39  {
40  bool canceled;
41  bool disposed;
42 
43  int currId = int.MinValue;
45  CancellationTokenRegistration[] linkedTokens;
46 
47  ManualResetEvent handle;
48 
49  internal static readonly CancellationTokenSource NoneSource = new CancellationTokenSource ();
50  internal static readonly CancellationTokenSource CanceledSource = new CancellationTokenSource ();
51 
52 #if NET_4_5
53  static readonly TimerCallback timer_callback;
54  Timer timer;
55 #endif
56 
57  static CancellationTokenSource ()
58  {
59  CanceledSource.canceled = true;
60 
61 #if NET_4_5
62  timer_callback = token => {
63  var cts = (CancellationTokenSource) token;
64  cts.Cancel ();
65  };
66 #endif
67  }
68 
69  // Spicy Pixel: AOT Fix. Dictionary requires a comparer.
70  class CancellationTokenRegistrationEqualityComparer : IEqualityComparer<CancellationTokenRegistration>
71  {
72  public static readonly CancellationTokenRegistrationEqualityComparer Default = new CancellationTokenRegistrationEqualityComparer();
73 
75  {
76  return x.Equals(y);
77  }
78 
79  public int GetHashCode (CancellationTokenRegistration obj)
80  {
81  return obj.GetHashCode();
82  }
83  }
84 
86  {
88  CancellationTokenRegistrationEqualityComparer.Default);
89  handle = new ManualResetEvent (false);
90  }
91 
92 #if NET_4_5
93  public CancellationTokenSource (int millisecondsDelay)
94  : this ()
95  {
96  if (millisecondsDelay < -1)
97  throw new ArgumentOutOfRangeException ("millisecondsDelay");
98 
99  if (millisecondsDelay != Timeout.Infinite)
100  timer = new Timer (timer_callback, this, millisecondsDelay, Timeout.Infinite);
101  }
102 
103  public CancellationTokenSource (TimeSpan delay)
104  : this (CheckTimeout (delay))
105  {
106  }
107 #endif
108 
109  public CancellationToken Token {
110  get {
111  CheckDisposed ();
112  return new CancellationToken (this);
113  }
114  }
115 
116  public bool IsCancellationRequested {
117  get {
118  return canceled;
119  }
120  }
121 
122  internal WaitHandle WaitHandle {
123  get {
124  CheckDisposed ();
125  return handle;
126  }
127  }
128 
129  public void Cancel ()
130  {
131  Cancel (false);
132  }
133 
134  // If parameter is true we throw exception as soon as they appear otherwise we aggregate them
135  public void Cancel (bool throwOnFirstException)
136  {
137  CheckDisposed ();
138 
139  if (canceled)
140  return;
141 
142  Thread.MemoryBarrier ();
143  canceled = true;
144 
145  handle.Set ();
146  if (linkedTokens != null)
147  UnregisterLinkedTokens ();
148 
149  List<Exception> exceptions = null;
150 
151  try {
152  Action cb;
153  for (int id = int.MinValue + 1; id <= currId; id++) {
154  if (!callbacks.TryRemove (new CancellationTokenRegistration (id, this), out cb))
155  continue;
156  if (cb == null)
157  continue;
158 
159  if (throwOnFirstException) {
160  cb ();
161  } else {
162  try {
163  cb ();
164  } catch (Exception e) {
165  if (exceptions == null)
166  exceptions = new List<Exception> ();
167 
168  exceptions.Add (e);
169  }
170  }
171  }
172  } finally {
173  callbacks.Clear ();
174  }
175 
176  if (exceptions != null)
177  throw new AggregateException (exceptions);
178  }
179 
180  /* This is the callback registered on linked tokens
181  * so that they don't throw an ODE if the callback
182  * is called concurrently with a Dispose
183  */
184  void SafeLinkedCancel ()
185  {
186  try {
187  Cancel ();
188  } catch (ObjectDisposedException) {}
189  }
190 
191 #if NET_4_5
192  public void CancelAfter (TimeSpan delay)
193  {
194  CancelAfter (CheckTimeout (delay));
195  }
196 
197  public void CancelAfter (int millisecondsDelay)
198  {
199  if (millisecondsDelay < -1)
200  throw new ArgumentOutOfRangeException ("millisecondsDelay");
201 
202  CheckDisposed ();
203 
204  if (canceled || millisecondsDelay == Timeout.Infinite)
205  return;
206 
207  if (timer == null) {
208  // Have to be carefull not to create secondary background timer
209  var t = new Timer (timer_callback, this, Timeout.Infinite, Timeout.Infinite);
210  if (AotInterlocked.CompareExchange (ref timer, t, null) != null)
211  t.Dispose ();
212  }
213 
214  timer.Change (millisecondsDelay, Timeout.Infinite);
215  }
216 #endif
217 
219  {
220  return CreateLinkedTokenSource (new [] { token1, token2 });
221  }
222 
224  {
225  if (tokens == null)
226  throw new ArgumentNullException ("tokens");
227 
228  if (tokens.Length == 0)
229  throw new ArgumentException ("Empty tokens array");
230 
232  Action action = src.SafeLinkedCancel;
233  var registrations = new List<CancellationTokenRegistration> (tokens.Length);
234 
235  foreach (CancellationToken token in tokens) {
236  if (token.CanBeCanceled)
237  registrations.Add (token.Register (action));
238  }
239  src.linkedTokens = registrations.ToArray ();
240 
241  return src;
242  }
243 
244  static int CheckTimeout (TimeSpan delay)
245  {
246  try {
247  return checked ((int) delay.TotalMilliseconds);
248  } catch (OverflowException) {
249  throw new ArgumentOutOfRangeException ("delay");
250  }
251  }
252 
253  void CheckDisposed ()
254  {
255  if (disposed)
256  throw new ObjectDisposedException (GetType ().Name);
257  }
258 
259  public void Dispose ()
260  {
261  Dispose (true);
262  }
263 
264 #if NET_4_5
265  protected virtual
266 #endif
267  void Dispose (bool disposing)
268  {
269  if (disposing && !disposed) {
270  Thread.MemoryBarrier ();
271  disposed = true;
272 
273  if (!canceled) {
274  Thread.MemoryBarrier ();
275  UnregisterLinkedTokens ();
276  callbacks = null;
277  }
278 #if NET_4_5
279  if (timer != null)
280  timer.Dispose ();
281 #endif
282  // Spicy Pixel: Must use the public interface (e.g. Close)
283  // handle.Dispose ();
284  handle.Close();
285  }
286  }
287 
288  void UnregisterLinkedTokens ()
289  {
290  var registrations = AotInterlocked.Exchange (ref linkedTokens, null);
291  if (registrations == null)
292  return;
293  foreach (var linked in registrations)
294  linked.Dispose ();
295  }
296 
297  internal CancellationTokenRegistration Register (Action callback, bool useSynchronizationContext)
298  {
299  CheckDisposed ();
300 
301  var tokenReg = new CancellationTokenRegistration (Interlocked.Increment (ref currId), this);
302 
303  /* If the source is already canceled we execute the callback immediately
304  * if not, we try to add it to the queue and if it is currently being processed
305  * we try to execute it back ourselves to be sure the callback is ran
306  */
307  if (canceled)
308  callback ();
309  else {
310  callbacks.TryAdd (tokenReg, callback);
311  if (canceled && callbacks.TryRemove (tokenReg, out callback))
312  callback ();
313  }
314 
315  return tokenReg;
316  }
317 
318  internal void RemoveCallback (CancellationTokenRegistration reg)
319  {
320  // Ignore call if the source has been disposed
321  if (disposed)
322  return;
323  Action dummy;
324  var cbs = callbacks;
325  if (cbs != null)
326  cbs.TryRemove (reg, out dummy);
327  }
328  }
329 }
330 #endif
static CancellationTokenSource CreateLinkedTokenSource(CancellationToken token1, CancellationToken token2)
Interlocked reference exchanges do not work with the older Mono AOT compiler so this type fudges arou...
static CancellationTokenSource CreateLinkedTokenSource(params CancellationToken[] tokens)
bool Equals(CancellationTokenRegistration other)
CancellationTokenRegistration Register(Action callback)