Merge pull request #217 from QuickJack/master
[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 || MOBILE
30 using System.Collections.Generic;
31
32 namespace System.Threading
33 {
34 #if !NET_4_5
35         sealed
36 #endif
37         public class CancellationTokenSource : IDisposable
38         {
39                 bool canceled;
40                 bool processed;
41                 bool disposed;
42                 
43                 int currId = int.MinValue;
44
45                 Dictionary<CancellationTokenRegistration, Action> callbacks;
46                 
47                 ManualResetEvent handle;
48                 readonly object syncRoot = new object ();
49                 
50                 internal static readonly CancellationTokenSource NoneSource = new CancellationTokenSource ();
51                 internal static readonly CancellationTokenSource CanceledSource = new CancellationTokenSource ();
52                 
53 #if NET_4_5
54                 static readonly TimerCallback timer_callback;
55                 Timer timer;
56 #endif
57
58                 static CancellationTokenSource ()
59                 {
60                         CanceledSource.processed = true;
61                         CanceledSource.canceled = true;
62
63 #if NET_4_5
64                         timer_callback = token => {
65                                 var cts = (CancellationTokenSource) token;
66                                 cts.Cancel ();
67                         };
68 #endif
69                 }
70
71                 public CancellationTokenSource ()
72                 {
73                         callbacks = new Dictionary<CancellationTokenRegistration, Action> ();
74                         handle = new ManualResetEvent (false);
75                 }
76
77 #if NET_4_5
78                 public CancellationTokenSource (int millisecondsDelay)
79                         : this ()
80                 {
81                         if (millisecondsDelay < -1)
82                                 throw new ArgumentOutOfRangeException ("millisecondsDelay");
83
84                         if (millisecondsDelay != Timeout.Infinite)
85                                 timer = new Timer (timer_callback, this, millisecondsDelay, Timeout.Infinite);
86                 }
87
88                 public CancellationTokenSource (TimeSpan delay)
89                         : this (CheckTimeout (delay))
90                 {
91                 }
92 #endif
93
94                 public CancellationToken Token {
95                         get {
96                                 CheckDisposed ();
97                                 return new CancellationToken (this);
98                         }
99                 }
100                 
101                 public bool IsCancellationRequested {
102                         get {
103                                 return canceled;
104                         }
105                 }
106                 
107                 internal WaitHandle WaitHandle {
108                         get {
109                                 CheckDisposed ();
110                                 return handle;
111                         }
112                 }
113                 
114                 public void Cancel ()
115                 {
116                         Cancel (false);
117                 }
118                 
119                 // If parameter is true we throw exception as soon as they appear otherwise we aggregate them
120                 public void Cancel (bool throwOnFirstException)
121                 {
122                         CheckDisposed ();
123
124                         canceled = true;
125                         handle.Set ();
126                         
127                         List<Exception> exceptions = null;
128                         
129                         lock (callbacks) {
130                                 try {
131                                         foreach (var item in callbacks) {
132                                                 if (throwOnFirstException) {
133                                                         item.Value ();
134                                                 } else {
135                                                         try {
136                                                                 item.Value ();
137                                                         } catch (Exception e) {
138                                                                 if (exceptions == null)
139                                                                         exceptions = new List<Exception> ();
140
141                                                                 exceptions.Add (e);
142                                                         }
143                                                 }
144                                         }
145                                 } finally {
146                                         callbacks.Clear ();
147                                 }
148                         }
149                         
150                         Thread.MemoryBarrier ();
151                         processed = true;
152                         
153                         if (exceptions != null)
154                                 throw new AggregateException (exceptions);
155                 }
156
157 #if NET_4_5
158                 public void CancelAfter (TimeSpan delay)
159                 {
160                         CancelAfter (CheckTimeout (delay));
161                 }
162
163                 public void CancelAfter (int millisecondsDelay)
164                 {
165                         if (millisecondsDelay < -1)
166                                 throw new ArgumentOutOfRangeException ("millisecondsDelay");
167
168                         CheckDisposed ();
169
170                         if (canceled || millisecondsDelay == Timeout.Infinite)
171                                 return;
172
173                         if (timer == null) {
174                                 // Have to be carefull not to create secondary background timer
175                                 var t = new Timer (timer_callback, this, Timeout.Infinite, Timeout.Infinite);
176                                 if (Interlocked.CompareExchange (ref timer, t, null) != null)
177                                         t.Dispose ();
178                         }
179
180                         timer.Change (millisecondsDelay, Timeout.Infinite);
181                 }
182 #endif
183
184                 public static CancellationTokenSource CreateLinkedTokenSource (CancellationToken token1, CancellationToken token2)
185                 {
186                         return CreateLinkedTokenSource (new [] { token1, token2 });
187                 }
188                 
189                 public static CancellationTokenSource CreateLinkedTokenSource (params CancellationToken[] tokens)
190                 {
191                         if (tokens == null)
192                                 throw new ArgumentNullException ("tokens");
193
194                         if (tokens.Length == 0)
195                                 throw new ArgumentException ("Empty tokens array");
196
197                         CancellationTokenSource src = new CancellationTokenSource ();
198                         Action action = src.Cancel;
199
200                         foreach (CancellationToken token in tokens) {
201                                 if (token.CanBeCanceled)
202                                         token.Register (action);
203                         }
204                         
205                         return src;
206                 }
207
208                 static int CheckTimeout (TimeSpan delay)
209                 {
210                         try {
211                                 return checked ((int) delay.TotalMilliseconds);
212                         } catch (OverflowException) {
213                                 throw new ArgumentOutOfRangeException ("delay");
214                         }
215                 }
216
217                 void CheckDisposed ()
218                 {
219                         if (disposed)
220                                 throw new ObjectDisposedException (GetType ().Name);
221                 }
222
223                 public void Dispose ()
224                 {
225                         Dispose (true);
226                 }
227
228 #if NET_4_5
229                 protected virtual
230 #endif
231                 void Dispose (bool disposing)
232                 {
233                         if (disposing && !disposed) {
234                                 disposed = true;
235
236                                 callbacks = null;
237 #if NET_4_5
238                                 if (timer != null)
239                                         timer.Dispose ();
240 #endif
241                                 handle.Dispose ();
242                         }
243                 }
244                 
245                 internal CancellationTokenRegistration Register (Action callback, bool useSynchronizationContext)
246                 {
247                         CheckDisposed ();
248
249                         var tokenReg = new CancellationTokenRegistration (Interlocked.Increment (ref currId), this);
250
251                         if (canceled) {
252                                 callback ();
253                         } else {
254                                 bool temp = false;
255                                 lock (syncRoot) {
256                                         if (!(temp = canceled))
257                                                 callbacks.Add (tokenReg, callback);
258                                 }
259                                 if (temp)
260                                         callback ();
261                         }
262                         
263                         return tokenReg;
264                 }
265                 
266                 internal void RemoveCallback (CancellationTokenRegistration tokenReg)
267                 {
268                         if (!canceled) {
269                                 lock (syncRoot) {
270                                         if (!canceled) {
271                                                 callbacks.Remove (tokenReg);
272                                                 return;
273                                         }
274                                 }
275                         }
276                         
277                         SpinWait sw = new SpinWait ();
278                         while (!processed)
279                                 sw.SpinOnce ();
280                         
281                 }
282         }
283 }
284 #endif