Merge pull request #901 from Blewzman/FixAggregateExceptionGetBaseException
[mono.git] / mcs / class / corlib / System.Threading / CancellationTokenSource.cs
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
30 using System.Collections.Generic;
31 using System.Collections.Concurrent;
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;
44                 ConcurrentDictionary<CancellationTokenRegistration, Action> callbacks;
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.CancelSafe ();
65                         };
66 #endif
67                 }
68
69                 public CancellationTokenSource ()
70                 {
71                         callbacks = new ConcurrentDictionary<CancellationTokenRegistration, Action> ();
72                         handle = new ManualResetEvent (false);
73                 }
74
75 #if NET_4_5
76                 public CancellationTokenSource (int millisecondsDelay)
77                         : this ()
78                 {
79                         if (millisecondsDelay < -1)
80                                 throw new ArgumentOutOfRangeException ("millisecondsDelay");
81
82                         if (millisecondsDelay != Timeout.Infinite)
83                                 timer = new Timer (timer_callback, this, millisecondsDelay, Timeout.Infinite);
84                 }
85
86                 public CancellationTokenSource (TimeSpan delay)
87                         : this (CheckTimeout (delay))
88                 {
89                 }
90 #endif
91
92                 public CancellationToken Token {
93                         get {
94                                 CheckDisposed ();
95                                 return new CancellationToken (this);
96                         }
97                 }
98                 
99                 public bool IsCancellationRequested {
100                         get {
101                                 return canceled;
102                         }
103                 }
104                 
105                 internal WaitHandle WaitHandle {
106                         get {
107                                 CheckDisposed ();
108                                 return handle;
109                         }
110                 }
111                 
112                 public void Cancel ()
113                 {
114                         Cancel (false);
115                 }
116                 
117                 // If parameter is true we throw exception as soon as they appear otherwise we aggregate them
118                 public void Cancel (bool throwOnFirstException)
119                 {
120                         CheckDisposed ();
121                         Cancellation (throwOnFirstException);
122                 }
123
124                 //
125                 // Don't throw ObjectDisposedException if the callback
126                 // is called concurrently with a Dispose
127                 //
128                 public void CancelSafe ()
129                 {
130                         if (!disposed)
131                                 Cancellation (true);
132                 }
133
134                 void Cancellation (bool throwOnFirstException)
135                 {
136                         if (canceled)
137                                 return;
138
139                         Thread.MemoryBarrier ();
140                         canceled = true;
141
142                         Thread.MemoryBarrier ();
143
144                         // Dispose might be running at same time
145                         if (!disposed)
146                                 handle.Set ();
147
148                         if (linkedTokens != null)
149                                 UnregisterLinkedTokens ();
150
151                         var cbs = callbacks;
152                         if (cbs == null)
153                                 return;
154
155                         List<Exception> exceptions = null;
156
157                         try {
158                                 Action cb;
159                                 for (int id = currId; id != int.MinValue; id--) {
160                                         if (!cbs.TryRemove (new CancellationTokenRegistration (id, this), out cb))
161                                                 continue;
162                                         if (cb == null)
163                                                 continue;
164
165                                         if (throwOnFirstException) {
166                                                 cb ();
167                                         } else {
168                                                 try {
169                                                         cb ();
170                                                 } catch (Exception e) {
171                                                         if (exceptions == null)
172                                                                 exceptions = new List<Exception> ();
173
174                                                         exceptions.Add (e);
175                                                 }
176                                         }
177                                 }
178                         } finally {
179                                 cbs.Clear ();
180                         }
181
182                         if (exceptions != null)
183                                 throw new AggregateException (exceptions);
184                 }
185
186 #if NET_4_5
187                 public void CancelAfter (TimeSpan delay)
188                 {
189                         CancelAfter (CheckTimeout (delay));
190                 }
191
192                 public void CancelAfter (int millisecondsDelay)
193                 {
194                         if (millisecondsDelay < -1)
195                                 throw new ArgumentOutOfRangeException ("millisecondsDelay");
196
197                         CheckDisposed ();
198
199                         if (canceled || millisecondsDelay == Timeout.Infinite)
200                                 return;
201
202                         if (timer == null) {
203                                 // Have to be carefull not to create secondary background timer
204                                 var t = new Timer (timer_callback, this, Timeout.Infinite, Timeout.Infinite);
205                                 if (Interlocked.CompareExchange (ref timer, t, null) != null)
206                                         t.Dispose ();
207                         }
208
209                         timer.Change (millisecondsDelay, Timeout.Infinite);
210                 }
211 #endif
212
213                 public static CancellationTokenSource CreateLinkedTokenSource (CancellationToken token1, CancellationToken token2)
214                 {
215                         return CreateLinkedTokenSource (new [] { token1, token2 });
216                 }
217                 
218                 public static CancellationTokenSource CreateLinkedTokenSource (params CancellationToken[] tokens)
219                 {
220                         if (tokens == null)
221                                 throw new ArgumentNullException ("tokens");
222
223                         if (tokens.Length == 0)
224                                 throw new ArgumentException ("Empty tokens array");
225
226                         CancellationTokenSource src = new CancellationTokenSource ();
227                         Action action = src.CancelSafe;
228                         var registrations = new List<CancellationTokenRegistration> (tokens.Length);
229
230                         foreach (CancellationToken token in tokens) {
231                                 if (token.CanBeCanceled)
232                                         registrations.Add (token.Register (action));
233                         }
234                         src.linkedTokens = registrations.ToArray ();
235                         
236                         return src;
237                 }
238
239                 static int CheckTimeout (TimeSpan delay)
240                 {
241                         try {
242                                 return checked ((int) delay.TotalMilliseconds);
243                         } catch (OverflowException) {
244                                 throw new ArgumentOutOfRangeException ("delay");
245                         }
246                 }
247
248                 void CheckDisposed ()
249                 {
250                         if (disposed)
251                                 throw new ObjectDisposedException (GetType ().Name);
252                 }
253
254                 public void Dispose ()
255                 {
256                         Dispose (true);
257                 }
258
259 #if NET_4_5
260                 protected virtual
261 #endif
262                 void Dispose (bool disposing)
263                 {
264                         if (disposing && !disposed) {
265                                 disposed = true;
266                                 Thread.MemoryBarrier ();
267
268                                 if (!canceled) {
269                                         UnregisterLinkedTokens ();
270                                         callbacks = null;
271                                 } else {
272                                         handle.WaitOne ();
273                                 }
274 #if NET_4_5
275                                 if (timer != null)
276                                         timer.Dispose ();
277 #endif
278
279                                 handle.Dispose ();
280                         }
281                 }
282
283                 void UnregisterLinkedTokens ()
284                 {
285                         var registrations = Interlocked.Exchange (ref linkedTokens, null);
286                         if (registrations == null)
287                                 return;
288                         foreach (var linked in registrations)
289                                 linked.Dispose ();
290                 }
291                 
292                 internal CancellationTokenRegistration Register (Action callback, bool useSynchronizationContext)
293                 {
294                         CheckDisposed ();
295
296                         var tokenReg = new CancellationTokenRegistration (Interlocked.Increment (ref currId), this);
297
298                         /* If the source is already canceled we execute the callback immediately
299                          * if not, we try to add it to the queue and if it is currently being processed
300                          * we try to execute it back ourselves to be sure the callback is ran
301                          */
302                         if (canceled)
303                                 callback ();
304                         else {
305                                 callbacks.TryAdd (tokenReg, callback);
306                                 if (canceled && callbacks.TryRemove (tokenReg, out callback))
307                                         callback ();
308                         }
309                         
310                         return tokenReg;
311                 }
312
313                 internal void RemoveCallback (CancellationTokenRegistration reg)
314                 {
315                         // Ignore call if the source has been disposed
316                         if (disposed)
317                                 return;
318                         Action dummy;
319                         var cbs = callbacks;
320                         if (cbs != null)
321                                 cbs.TryRemove (reg, out dummy);
322                 }
323         }
324 }
325 #endif