Initial commit
[mono.git] / mcs / class / referencesource / mscorlib / system / threading / CancellationToken.cs
1 // ==++==
2 // 
3 //   Copyright (c) Microsoft Corporation.  All rights reserved.
4 // 
5 // ==--==
6 //
7 // <OWNER>[....]</OWNER>
8 ////////////////////////////////////////////////////////////////////////////////
9
10 #pragma warning disable 0420 // turn off 'a reference to a volatile field will not be treated as volatile' during CAS.
11
12 using System;
13 using System.Diagnostics;
14 using System.Runtime.InteropServices;
15 using System.Security.Permissions;
16 using System.Diagnostics.Contracts;
17 using System.Runtime;
18 using System.Runtime.CompilerServices;
19 using System.Security;
20
21 namespace System.Threading
22 {
23     /// <summary>
24     /// Propagates notification that operations should be canceled.
25     /// </summary>
26     /// <remarks>
27     /// <para>
28     /// A <see cref="CancellationToken"/> may be created directly in an unchangeable canceled or non-canceled state
29     /// using the CancellationToken's constructors. However, to have a CancellationToken that can change 
30     /// from a non-canceled to a canceled state, 
31     /// <see cref="System.Threading.CancellationTokenSource">CancellationTokenSource</see> must be used.
32     /// CancellationTokenSource exposes the associated CancellationToken that may be canceled by the source through its 
33     /// <see cref="System.Threading.CancellationTokenSource.Token">Token</see> property. 
34     /// </para>
35     /// <para>
36     /// Once canceled, a token may not transition to a non-canceled state, and a token whose 
37     /// <see cref="CanBeCanceled"/> is false will never change to one that can be canceled.
38     /// </para>
39     /// <para>
40     /// All members of this struct are thread-safe and may be used concurrently from multiple threads.
41     /// </para>
42     /// </remarks>
43     [ComVisible(false)]
44     [HostProtection(Synchronization = true, ExternalThreading = true)]
45     [DebuggerDisplay("IsCancellationRequested = {IsCancellationRequested}")]
46     public struct CancellationToken
47     {
48         // The backing TokenSource.  
49         // if null, it implicitly represents the same thing as new CancellationToken(false).
50         // When required, it will be instantiated to reflect this.
51         private CancellationTokenSource m_source;
52         //!! warning. If more fields are added, the assumptions in CreateLinkedToken may no longer be valid
53
54         /* Properties */
55
56         /// <summary>
57         /// Returns an empty CancellationToken value.
58         /// </summary>
59         /// <remarks>
60         /// The <see cref="CancellationToken"/> value returned by this property will be non-cancelable by default.
61         /// </remarks>
62         public static CancellationToken None
63         {
64 #if !FEATURE_CORECLR
65             [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
66 #endif
67             get { return default(CancellationToken); }
68         }
69
70         /// <summary>
71         /// Gets whether cancellation has been requested for this token.
72         /// </summary>
73         /// <value>Whether cancellation has been requested for this token.</value>
74         /// <remarks>
75         /// <para>
76         /// This property indicates whether cancellation has been requested for this token, 
77         /// either through the token initially being construted in a canceled state, or through
78         /// calling <see cref="System.Threading.CancellationTokenSource.Cancel()">Cancel</see> 
79         /// on the token's associated <see cref="CancellationTokenSource"/>.
80         /// </para>
81         /// <para>
82         /// If this property is true, it only guarantees that cancellation has been requested.  
83         /// It does not guarantee that every registered handler
84         /// has finished executing, nor that cancellation requests have finished propagating
85         /// to all registered handlers.  Additional synchronization may be required,
86         /// particularly in situations where related objects are being canceled concurrently.
87         /// </para>
88         /// </remarks>
89         public bool IsCancellationRequested 
90         {
91 #if !FEATURE_CORECLR
92             [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
93 #endif
94             get
95             {
96                 return m_source != null && m_source.IsCancellationRequested;
97             }
98         }
99         
100         /// <summary>
101         /// Gets whether this token is capable of being in the canceled state.
102         /// </summary>
103         /// <remarks>
104         /// If CanBeCanceled returns false, it is guaranteed that the token will never transition
105         /// into a canceled state, meaning that <see cref="IsCancellationRequested"/> will never
106         /// return true.
107         /// </remarks>
108         public bool CanBeCanceled
109         {
110 #if !FEATURE_CORECLR
111             [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
112 #endif
113             get
114             {
115                 return m_source != null && m_source.CanBeCanceled;
116             }
117         }
118
119         /// <summary>
120         /// Gets a <see cref="T:System.Threading.WaitHandle"/> that is signaled when the token is canceled.</summary>
121         /// <remarks>
122         /// Accessing this property causes a <see cref="T:System.Threading.WaitHandle">WaitHandle</see>
123         /// to be instantiated.  It is preferable to only use this property when necessary, and to then
124         /// dispose the associated <see cref="CancellationTokenSource"/> instance at the earliest opportunity (disposing
125         /// the source will dispose of this allocated handle).  The handle should not be closed or disposed directly.
126         /// </remarks>
127         /// <exception cref="T:System.ObjectDisposedException">The associated <see
128         /// cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> has been disposed.</exception>
129         public WaitHandle WaitHandle
130         {
131             get
132             {
133                 if (m_source == null)
134                 {
135                     InitializeDefaultSource();
136                 }
137
138                 return m_source.WaitHandle;
139             }
140         }
141
142         // public CancellationToken()
143         // this constructor is implicit for structs
144         //   -> this should behaves exactly as for new CancellationToken(false)
145
146         /// <summary>
147         /// Internal constructor only a CancellationTokenSource should create a CancellationToken
148         /// </summary>
149 #if !FEATURE_CORECLR
150         [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
151 #endif
152         internal CancellationToken(CancellationTokenSource source)
153         {
154             m_source = source;
155         }
156
157         /// <summary>
158         /// Initializes the <see cref="T:System.Threading.CancellationToken">CancellationToken</see>.
159         /// </summary>
160         /// <param name="canceled">
161         /// The canceled state for the token.
162         /// </param>
163         /// <remarks>
164         /// Tokens created with this constructor will remain in the canceled state specified
165         /// by the <paramref name="canceled"/> parameter.  If <paramref name="canceled"/> is false,
166         /// both <see cref="CanBeCanceled"/> and <see cref="IsCancellationRequested"/> will be false.
167         /// If <paramref name="canceled"/> is true,
168         /// both <see cref="CanBeCanceled"/> and <see cref="IsCancellationRequested"/> will be true. 
169         /// </remarks>
170         public CancellationToken(bool canceled) :
171             this()
172         {
173             if(canceled)
174                 m_source = CancellationTokenSource.InternalGetStaticSource(canceled);
175         }
176
177         /* Methods */
178         
179
180         private readonly static Action<Object> s_ActionToActionObjShunt = new Action<Object>(ActionToActionObjShunt);
181         private static void ActionToActionObjShunt(object obj)
182         {
183             Action action = obj as Action;
184             Contract.Assert(action != null, "Expected an Action here");
185             action();
186         }
187
188         /// <summary>
189         /// Registers a delegate that will be called when this <see cref="T:System.Threading.CancellationToken">CancellationToken</see> is canceled.
190         /// </summary>
191         /// <remarks>
192         /// <para>
193         /// If this token is already in the canceled state, the
194         /// delegate will be run immediately and synchronously. Any exception the delegate generates will be
195         /// propagated out of this method call.
196         /// </para>
197         /// <para>
198         /// The current <see cref="System.Threading.ExecutionContext">ExecutionContext</see>, if one exists, will be captured
199         /// along with the delegate and will be used when executing it.
200         /// </para>
201         /// </remarks>
202         /// <param name="callback">The delegate to be executed when the <see cref="T:System.Threading.CancellationToken">CancellationToken</see> is canceled.</param>
203         /// <returns>The <see cref="T:System.Threading.CancellationTokenRegistration"/> instance that can 
204         /// be used to deregister the callback.</returns>
205         /// <exception cref="T:System.ArgumentNullException"><paramref name="callback"/> is null.</exception>
206         /// <exception cref="T:System.ObjectDisposedException">The associated <see
207         /// cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> has been disposed.</exception>
208         public CancellationTokenRegistration Register(Action callback)
209         {
210             if (callback == null)
211                 throw new ArgumentNullException("callback");
212             
213             return Register(
214                 s_ActionToActionObjShunt,
215                 callback,
216                 false, // useSync=false
217                 true   // useExecutionContext=true
218              );
219         }
220
221         /// <summary>
222         /// Registers a delegate that will be called when this 
223         /// <see cref="T:System.Threading.CancellationToken">CancellationToken</see> is canceled.
224         /// </summary>
225         /// <remarks>
226         /// <para>
227         /// If this token is already in the canceled state, the
228         /// delegate will be run immediately and synchronously. Any exception the delegate generates will be
229         /// propagated out of this method call.
230         /// </para>
231         /// <para>
232         /// The current <see cref="System.Threading.ExecutionContext">ExecutionContext</see>, if one exists, will be captured
233         /// along with the delegate and will be used when executing it.
234         /// </para>
235         /// </remarks>
236         /// <param name="callback">The delegate to be executed when the <see cref="T:System.Threading.CancellationToken">CancellationToken</see> is canceled.</param>
237         /// <param name="useSynchronizationContext">A Boolean value that indicates whether to capture
238         /// the current <see cref="T:System.Threading.SynchronizationContext">SynchronizationContext</see> and use it
239         /// when invoking the <paramref name="callback"/>.</param>
240         /// <returns>The <see cref="T:System.Threading.CancellationTokenRegistration"/> instance that can 
241         /// be used to deregister the callback.</returns>
242         /// <exception cref="T:System.ArgumentNullException"><paramref name="callback"/> is null.</exception>
243         /// <exception cref="T:System.ObjectDisposedException">The associated <see
244         /// cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> has been disposed.</exception>
245         public CancellationTokenRegistration Register(Action callback, bool useSynchronizationContext)
246         {
247             if (callback == null)
248                 throw new ArgumentNullException("callback");
249             
250             return Register(
251                 s_ActionToActionObjShunt,
252                 callback,
253                 useSynchronizationContext,
254                 true   // useExecutionContext=true
255              );
256         }
257
258         /// <summary>
259         /// Registers a delegate that will be called when this 
260         /// <see cref="T:System.Threading.CancellationToken">CancellationToken</see> is canceled.
261         /// </summary>
262         /// <remarks>
263         /// <para>
264         /// If this token is already in the canceled state, the
265         /// delegate will be run immediately and synchronously. Any exception the delegate generates will be
266         /// propagated out of this method call.
267         /// </para>
268         /// <para>
269         /// The current <see cref="System.Threading.ExecutionContext">ExecutionContext</see>, if one exists, will be captured
270         /// along with the delegate and will be used when executing it.
271         /// </para>
272         /// </remarks>
273         /// <param name="callback">The delegate to be executed when the <see cref="T:System.Threading.CancellationToken">CancellationToken</see> is canceled.</param>
274         /// <param name="state">The state to pass to the <paramref name="callback"/> when the delegate is invoked.  This may be null.</param>
275         /// <returns>The <see cref="T:System.Threading.CancellationTokenRegistration"/> instance that can 
276         /// be used to deregister the callback.</returns>
277         /// <exception cref="T:System.ArgumentNullException"><paramref name="callback"/> is null.</exception>
278         /// <exception cref="T:System.ObjectDisposedException">The associated <see
279         /// cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> has been disposed.</exception>
280         public CancellationTokenRegistration Register(Action<Object> callback, Object state)
281         {
282             if (callback == null)
283                 throw new ArgumentNullException("callback");
284
285             return Register(
286                 callback,
287                 state,
288                 false, // useSync=false
289                 true   // useExecutionContext=true
290              );
291         }
292
293         /// <summary>
294         /// Registers a delegate that will be called when this 
295         /// <see cref="T:System.Threading.CancellationToken">CancellationToken</see> is canceled.
296         /// </summary>
297         /// <remarks>
298         /// <para>
299         /// If this token is already in the canceled state, the
300         /// delegate will be run immediately and synchronously. Any exception the delegate generates will be
301         /// propagated out of this method call.
302         /// </para>
303         /// <para>
304         /// The current <see cref="System.Threading.ExecutionContext">ExecutionContext</see>, if one exists, 
305         /// will be captured along with the delegate and will be used when executing it.
306         /// </para>
307         /// </remarks>
308         /// <param name="callback">The delegate to be executed when the <see cref="T:System.Threading.CancellationToken">CancellationToken</see> is canceled.</param>
309         /// <param name="state">The state to pass to the <paramref name="callback"/> when the delegate is invoked.  This may be null.</param>
310         /// <param name="useSynchronizationContext">A Boolean value that indicates whether to capture
311         /// the current <see cref="T:System.Threading.SynchronizationContext">SynchronizationContext</see> and use it
312         /// when invoking the <paramref name="callback"/>.</param>
313         /// <returns>The <see cref="T:System.Threading.CancellationTokenRegistration"/> instance that can 
314         /// be used to deregister the callback.</returns>
315         /// <exception cref="T:System.ArgumentNullException"><paramref name="callback"/> is null.</exception>
316         /// <exception cref="T:System.ObjectDisposedException">The associated <see
317         /// cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> has been disposed.</exception>
318         public CancellationTokenRegistration Register(Action<Object> callback, Object state, bool useSynchronizationContext)
319         {
320             return Register(
321                 callback,
322                 state,
323                 useSynchronizationContext,
324                 true   // useExecutionContext=true
325              );
326         }
327         
328         // helper for internal registration needs that don't require an EC capture (e.g. creating linked token sources, or registering unstarted TPL tasks)
329         // has a handy signature, and skips capturing execution context.
330         internal CancellationTokenRegistration InternalRegisterWithoutEC(Action<object> callback, Object state)
331         {
332             return Register(
333                 callback,
334                 state,
335                 false, // useSyncContext=false
336                 false  // useExecutionContext=false
337              );
338         }
339
340         // the real work..
341         [SecuritySafeCritical]
342         [MethodImpl(MethodImplOptions.NoInlining)]
343         private CancellationTokenRegistration Register(Action<Object> callback, Object state, bool useSynchronizationContext, bool useExecutionContext)
344         {
345             StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
346
347             if (callback == null)
348                 throw new ArgumentNullException("callback");
349
350             if (CanBeCanceled == false)
351             {
352                 return new CancellationTokenRegistration(); // nothing to do for tokens than can never reach the canceled state. Give them a dummy registration.
353             }
354
355             // Capture [....]/execution contexts if required.
356             // Note: Only capture [....]/execution contexts if IsCancellationRequested = false
357             // as we know that if it is true that the callback will just be called synchronously.
358
359             SynchronizationContext capturedSyncContext = null;
360             ExecutionContext capturedExecutionContext = null;
361             if (!IsCancellationRequested)
362             {
363                 if (useSynchronizationContext)
364                     capturedSyncContext = SynchronizationContext.Current;
365                 if (useExecutionContext)
366                     capturedExecutionContext = ExecutionContext.Capture(
367                         ref stackMark, ExecutionContext.CaptureOptions.OptimizeDefaultCase); // ideally we'd also use IgnoreSyncCtx, but that could break compat
368             }
369
370             // Register the callback with the source.
371             return m_source.InternalRegister(callback, state, capturedSyncContext, capturedExecutionContext);
372         }
373
374         /// <summary>
375         /// Determines whether the current <see cref="T:System.Threading.CancellationToken">CancellationToken</see> instance is equal to the 
376         /// specified token.
377         /// </summary>
378         /// <param name="other">The other <see cref="T:System.Threading.CancellationToken">CancellationToken</see> to which to compare this
379         /// instance.</param>
380         /// <returns>True if the instances are equal; otherwise, false. Two tokens are equal if they are associated
381         /// with the same <see cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> or if they were both constructed 
382         /// from public CancellationToken constructors and their <see cref="IsCancellationRequested"/> values are equal.</returns>
383         public bool Equals(CancellationToken other)
384         {
385             //if both sources are null, then both tokens represent the Empty token.
386             if (m_source == null && other.m_source == null)
387             {
388                 return true;
389             }
390
391             // one is null but other has inflated the default source
392             // these are only equal if the inflated one is the staticSource(false)
393             if (m_source == null)
394             {
395                 return other.m_source == CancellationTokenSource.InternalGetStaticSource(false);
396             }
397             
398             if (other.m_source == null)
399             {
400                 return m_source == CancellationTokenSource.InternalGetStaticSource(false);
401             }
402
403             // general case, we check if the sources are identical
404             
405             return m_source == other.m_source;
406         }
407
408         /// <summary>
409         /// Determines whether the current <see cref="T:System.Threading.CancellationToken">CancellationToken</see> instance is equal to the 
410         /// specified <see cref="T:System.Object"/>.
411         /// </summary>
412         /// <param name="other">The other object to which to compare this instance.</param>
413         /// <returns>True if <paramref name="other"/> is a <see cref="T:System.Threading.CancellationToken">CancellationToken</see>
414         /// and if the two instances are equal; otherwise, false. Two tokens are equal if they are associated
415         /// with the same <see cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> or if they were both constructed 
416         /// from public CancellationToken constructors and their <see cref="IsCancellationRequested"/> values are equal.</returns>
417         /// <exception cref="T:System.ObjectDisposedException">An associated <see
418         /// cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> has been disposed.</exception>
419         public override bool Equals(Object other)
420         {
421             if (other is CancellationToken)
422             {
423                 return Equals((CancellationToken) other);
424             }
425
426             return false;
427         }
428
429         /// <summary>
430         /// Serves as a hash function for a <see cref="T:System.Threading.CancellationToken">CancellationToken</see>.
431         /// </summary>
432         /// <returns>A hash code for the current <see cref="T:System.Threading.CancellationToken">CancellationToken</see> instance.</returns>
433         public override Int32 GetHashCode()
434         {
435             if (m_source == null)
436             {
437                 // link to the common source so that we have a source to interrogate.
438                 return CancellationTokenSource.InternalGetStaticSource(false).GetHashCode();
439             }
440
441             return m_source.GetHashCode(); 
442         }
443         
444         /// <summary>
445         /// Determines whether two <see cref="T:System.Threading.CancellationToken">CancellationToken</see> instances are equal.
446         /// </summary>
447         /// <param name="left">The first instance.</param>
448         /// <param name="right">The second instance.</param>
449         /// <returns>True if the instances are equal; otherwise, false.</returns>
450         /// <exception cref="T:System.ObjectDisposedException">An associated <see
451         /// cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> has been disposed.</exception>
452         public static bool operator ==(CancellationToken left, CancellationToken right)
453         {
454             return left.Equals(right);
455         }
456
457         /// <summary>
458         /// Determines whether two <see cref="T:System.Threading.CancellationToken">CancellationToken</see> instances are not equal.
459         /// </summary>
460         /// <param name="left">The first instance.</param>
461         /// <param name="right">The second instance.</param>
462         /// <returns>True if the instances are not equal; otherwise, false.</returns>
463         /// <exception cref="T:System.ObjectDisposedException">An associated <see
464         /// cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> has been disposed.</exception>
465         public static bool operator !=(CancellationToken left, CancellationToken right)
466         {
467             return !left.Equals(right);
468         }
469
470         /// <summary>
471         /// Throws a <see cref="T:System.OperationCanceledException">OperationCanceledException</see> if
472         /// this token has had cancellation requested.
473         /// </summary>
474         /// <remarks>
475         /// This method provides functionality equivalent to:
476         /// <code>
477         /// if (token.IsCancellationRequested) 
478         ///    throw new OperationCanceledException(token);
479         /// </code>
480         /// </remarks>
481         /// <exception cref="System.OperationCanceledException">The token has had cancellation requested.</exception>
482         /// <exception cref="T:System.ObjectDisposedException">The associated <see
483         /// cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> has been disposed.</exception>
484 #if !FEATURE_CORECLR
485         [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
486 #endif
487         public void ThrowIfCancellationRequested()
488         {
489             if (IsCancellationRequested) 
490                 ThrowOperationCanceledException();
491         }
492
493         // Throw an ODE if this CancellationToken's source is disposed.
494         internal void ThrowIfSourceDisposed()
495         {
496             if ((m_source != null) && m_source.IsDisposed)
497                 ThrowObjectDisposedException();
498         }
499
500         // Throws an OCE; separated out to enable better inlining of ThrowIfCancellationRequested
501         private void ThrowOperationCanceledException()
502         {
503             throw new OperationCanceledException(Environment.GetResourceString("OperationCanceled"), this);
504         }
505
506         private static void ThrowObjectDisposedException()
507         {
508             throw new ObjectDisposedException(null, Environment.GetResourceString("CancellationToken_SourceDisposed"));
509         }
510
511         // -----------------------------------
512         // Private helpers
513         
514         private void InitializeDefaultSource()
515         {
516             // Lazy is slower, and although multiple threads may ---- and set m_source repeatedly, the ---- is benign.
517             // Alternative: LazyInititalizer.EnsureInitialized(ref m_source, ()=>CancellationTokenSource.InternalGetStaticSource(false));
518
519             m_source = CancellationTokenSource.InternalGetStaticSource(false);
520         }
521     }
522 }