1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 // =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
6 //
7 //
8 //
9 // TaskCompletionSource<TResult> is the producer end of an unbound future.  Its
10 // Task member may be distributed as the consumer end of the future.
11 //
12 // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
13 
14 using System;
15 using System.Diagnostics;
16 using System.Collections.Generic;
17 using System.Runtime.CompilerServices;
18 using System.Runtime.ExceptionServices;
19 using System.Threading;
20 
21 // Disable the "reference to volatile field not treated as volatile" error.
22 #pragma warning disable 0420
23 
24 namespace System.Threading.Tasks
25 {
26     /// <summary>
27     /// Represents the producer side of a <see cref="T:System.Threading.Tasks.Task{TResult}"/> unbound to a
28     /// delegate, providing access to the consumer side through the <see cref="Task"/> property.
29     /// </summary>
30     /// <remarks>
31     /// <para>
32     /// It is often the case that a <see cref="T:System.Threading.Tasks.Task{TResult}"/> is desired to
33     /// represent another asynchronous operation.
34     /// <see cref="TaskCompletionSource{TResult}">TaskCompletionSource</see> is provided for this purpose. It enables
35     /// the creation of a task that can be handed out to consumers, and those consumers can use the members
36     /// of the task as they would any other. However, unlike most tasks, the state of a task created by a
37     /// TaskCompletionSource is controlled explicitly by the methods on TaskCompletionSource. This enables the
38     /// completion of the external asynchronous operation to be propagated to the underlying Task. The
39     /// separation also ensures that consumers are not able to transition the state without access to the
40     /// corresponding TaskCompletionSource.
41     /// </para>
42     /// <para>
43     /// All members of <see cref="TaskCompletionSource{TResult}"/> are thread-safe
44     /// and may be used from multiple threads concurrently.
45     /// </para>
46     /// </remarks>
47     /// <typeparam name="TResult">The type of the result value associated with this <see
48     /// cref="TaskCompletionSource{TResult}"/>.</typeparam>
49     public class TaskCompletionSource<TResult>
50     {
51         private readonly Task<TResult> _task;
52 
53         /// <summary>
54         /// Creates a <see cref="TaskCompletionSource{TResult}"/>.
55         /// </summary>
TaskCompletionSource()56         public TaskCompletionSource()
57         {
58             _task = new Task<TResult>();
59         }
60 
61         /// <summary>
62         /// Creates a <see cref="TaskCompletionSource{TResult}"/>
63         /// with the specified options.
64         /// </summary>
65         /// <remarks>
66         /// The <see cref="T:System.Threading.Tasks.Task{TResult}"/> created
67         /// by this instance and accessible through its <see cref="Task"/> property
68         /// will be instantiated using the specified <paramref name="creationOptions"/>.
69         /// </remarks>
70         /// <param name="creationOptions">The options to use when creating the underlying
71         /// <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</param>
72         /// <exception cref="T:System.ArgumentOutOfRangeException">
73         /// The <paramref name="creationOptions"/> represent options invalid for use
74         /// with a <see cref="TaskCompletionSource{TResult}"/>.
75         /// </exception>
TaskCompletionSource(TaskCreationOptions creationOptions)76         public TaskCompletionSource(TaskCreationOptions creationOptions)
77             : this(null, creationOptions)
78         {
79         }
80 
81         /// <summary>
82         /// Creates a <see cref="TaskCompletionSource{TResult}"/>
83         /// with the specified state.
84         /// </summary>
85         /// <param name="state">The state to use as the underlying
86         /// <see cref="T:System.Threading.Tasks.Task{TResult}"/>'s AsyncState.</param>
TaskCompletionSource(object state)87         public TaskCompletionSource(object state)
88             : this(state, TaskCreationOptions.None)
89         {
90         }
91 
92         /// <summary>
93         /// Creates a <see cref="TaskCompletionSource{TResult}"/> with
94         /// the specified state and options.
95         /// </summary>
96         /// <param name="creationOptions">The options to use when creating the underlying
97         /// <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</param>
98         /// <param name="state">The state to use as the underlying
99         /// <see cref="T:System.Threading.Tasks.Task{TResult}"/>'s AsyncState.</param>
100         /// <exception cref="T:System.ArgumentOutOfRangeException">
101         /// The <paramref name="creationOptions"/> represent options invalid for use
102         /// with a <see cref="TaskCompletionSource{TResult}"/>.
103         /// </exception>
TaskCompletionSource(object state, TaskCreationOptions creationOptions)104         public TaskCompletionSource(object state, TaskCreationOptions creationOptions)
105         {
106             _task = new Task<TResult>(state, creationOptions);
107         }
108 
109 
110         /// <summary>
111         /// Gets the <see cref="T:System.Threading.Tasks.Task{TResult}"/> created
112         /// by this <see cref="TaskCompletionSource{TResult}"/>.
113         /// </summary>
114         /// <remarks>
115         /// This property enables a consumer access to the <see
116         /// cref="T:System.Threading.Tasks.Task{TResult}"/> that is controlled by this instance.
117         /// The <see cref="SetResult"/>, <see cref="SetException(System.Exception)"/>,
118         /// <see cref="SetException(System.Collections.Generic.IEnumerable{System.Exception})"/>, and <see cref="SetCanceled"/>
119         /// methods (and their "Try" variants) on this instance all result in the relevant state
120         /// transitions on this underlying Task.
121         /// </remarks>
122         public Task<TResult> Task => _task;
123 
124         /// <summary>Spins until the underlying task is completed.</summary>
125         /// <remarks>This should only be called if the task is in the process of being completed by another thread.</remarks>
SpinUntilCompleted()126         private void SpinUntilCompleted()
127         {
128             // Spin wait until the completion is finalized by another thread.
129             var sw = new SpinWait();
130             while (!_task.IsCompleted)
131                 sw.SpinOnce();
132         }
133 
134         /// <summary>
135         /// Attempts to transition the underlying
136         /// <see cref="T:System.Threading.Tasks.Task{TResult}"/> into the
137         /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">Faulted</see>
138         /// state.
139         /// </summary>
140         /// <param name="exception">The exception to bind to this <see
141         /// cref="T:System.Threading.Tasks.Task{TResult}"/>.</param>
142         /// <returns>True if the operation was successful; otherwise, false.</returns>
143         /// <remarks>This operation will return false if the
144         /// <see cref="T:System.Threading.Tasks.Task{TResult}"/> is already in one
145         /// of the three final states:
146         /// <see cref="System.Threading.Tasks.TaskStatus.RanToCompletion">RanToCompletion</see>,
147         /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">Faulted</see>, or
148         /// <see cref="System.Threading.Tasks.TaskStatus.Canceled">Canceled</see>.
149         /// </remarks>
150         /// <exception cref="T:System.ArgumentNullException">The <paramref name="exception"/> argument is null.</exception>
151         /// <exception cref="T:System.ObjectDisposedException">The <see cref="Task"/> was disposed.</exception>
TrySetException(Exception exception)152         public bool TrySetException(Exception exception)
153         {
154             if (exception == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.exception);
155 
156             bool rval = _task.TrySetException(exception);
157             if (!rval && !_task.IsCompleted) SpinUntilCompleted();
158             return rval;
159         }
160 
161         /// <summary>
162         /// Attempts to transition the underlying
163         /// <see cref="T:System.Threading.Tasks.Task{TResult}"/> into the
164         /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">Faulted</see>
165         /// state.
166         /// </summary>
167         /// <param name="exceptions">The collection of exceptions to bind to this <see
168         /// cref="T:System.Threading.Tasks.Task{TResult}"/>.</param>
169         /// <returns>True if the operation was successful; otherwise, false.</returns>
170         /// <remarks>This operation will return false if the
171         /// <see cref="T:System.Threading.Tasks.Task{TResult}"/> is already in one
172         /// of the three final states:
173         /// <see cref="System.Threading.Tasks.TaskStatus.RanToCompletion">RanToCompletion</see>,
174         /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">Faulted</see>, or
175         /// <see cref="System.Threading.Tasks.TaskStatus.Canceled">Canceled</see>.
176         /// </remarks>
177         /// <exception cref="T:System.ArgumentNullException">The <paramref name="exceptions"/> argument is null.</exception>
178         /// <exception cref="T:System.ArgumentException">There are one or more null elements in <paramref name="exceptions"/>.</exception>
179         /// <exception cref="T:System.ArgumentException">The <paramref name="exceptions"/> collection is empty.</exception>
180         /// <exception cref="T:System.ObjectDisposedException">The <see cref="Task"/> was disposed.</exception>
TrySetException(IEnumerable<Exception> exceptions)181         public bool TrySetException(IEnumerable<Exception> exceptions)
182         {
183             if (exceptions == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.exceptions);
184 
185             List<Exception> defensiveCopy = new List<Exception>();
186             foreach (Exception e in exceptions)
187             {
188                 if (e == null)
189                     ThrowHelper.ThrowArgumentException(ExceptionResource.TaskCompletionSourceT_TrySetException_NullException, ExceptionArgument.exceptions);
190                 defensiveCopy.Add(e);
191             }
192 
193             if (defensiveCopy.Count == 0)
194                 ThrowHelper.ThrowArgumentException(ExceptionResource.TaskCompletionSourceT_TrySetException_NoExceptions, ExceptionArgument.exceptions);
195 
196             bool rval = _task.TrySetException(defensiveCopy);
197             if (!rval && !_task.IsCompleted) SpinUntilCompleted();
198             return rval;
199         }
200 
201         /// <summary>
202         /// Transitions the underlying
203         /// <see cref="T:System.Threading.Tasks.Task{TResult}"/> into the
204         /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">Faulted</see>
205         /// state.
206         /// </summary>
207         /// <param name="exception">The exception to bind to this <see
208         /// cref="T:System.Threading.Tasks.Task{TResult}"/>.</param>
209         /// <exception cref="T:System.ArgumentNullException">The <paramref name="exception"/> argument is null.</exception>
210         /// <exception cref="T:System.InvalidOperationException">
211         /// The underlying <see cref="T:System.Threading.Tasks.Task{TResult}"/> is already in one
212         /// of the three final states:
213         /// <see cref="System.Threading.Tasks.TaskStatus.RanToCompletion">RanToCompletion</see>,
214         /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">Faulted</see>, or
215         /// <see cref="System.Threading.Tasks.TaskStatus.Canceled">Canceled</see>.
216         /// </exception>
217         /// <exception cref="T:System.ObjectDisposedException">The <see cref="Task"/> was disposed.</exception>
SetException(Exception exception)218         public void SetException(Exception exception)
219         {
220             if (exception == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.exception);
221 
222             if (!TrySetException(exception))
223             {
224                 ThrowHelper.ThrowInvalidOperationException(ExceptionResource.TaskT_TransitionToFinal_AlreadyCompleted);
225             }
226         }
227 
228         /// <summary>
229         /// Transitions the underlying
230         /// <see cref="T:System.Threading.Tasks.Task{TResult}"/> into the
231         /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">Faulted</see>
232         /// state.
233         /// </summary>
234         /// <param name="exceptions">The collection of exceptions to bind to this <see
235         /// cref="T:System.Threading.Tasks.Task{TResult}"/>.</param>
236         /// <exception cref="T:System.ArgumentNullException">The <paramref name="exceptions"/> argument is null.</exception>
237         /// <exception cref="T:System.ArgumentException">There are one or more null elements in <paramref name="exceptions"/>.</exception>
238         /// <exception cref="T:System.InvalidOperationException">
239         /// The underlying <see cref="T:System.Threading.Tasks.Task{TResult}"/> is already in one
240         /// of the three final states:
241         /// <see cref="System.Threading.Tasks.TaskStatus.RanToCompletion">RanToCompletion</see>,
242         /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">Faulted</see>, or
243         /// <see cref="System.Threading.Tasks.TaskStatus.Canceled">Canceled</see>.
244         /// </exception>
245         /// <exception cref="T:System.ObjectDisposedException">The <see cref="Task"/> was disposed.</exception>
SetException(IEnumerable<Exception> exceptions)246         public void SetException(IEnumerable<Exception> exceptions)
247         {
248             if (!TrySetException(exceptions))
249             {
250                 ThrowHelper.ThrowInvalidOperationException(ExceptionResource.TaskT_TransitionToFinal_AlreadyCompleted);
251             }
252         }
253 
254 
255         /// <summary>
256         /// Attempts to transition the underlying
257         /// <see cref="T:System.Threading.Tasks.Task{TResult}"/> into the
258         /// <see cref="System.Threading.Tasks.TaskStatus.RanToCompletion">RanToCompletion</see>
259         /// state.
260         /// </summary>
261         /// <param name="result">The result value to bind to this <see
262         /// cref="T:System.Threading.Tasks.Task{TResult}"/>.</param>
263         /// <returns>True if the operation was successful; otherwise, false.</returns>
264         /// <remarks>This operation will return false if the
265         /// <see cref="T:System.Threading.Tasks.Task{TResult}"/> is already in one
266         /// of the three final states:
267         /// <see cref="System.Threading.Tasks.TaskStatus.RanToCompletion">RanToCompletion</see>,
268         /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">Faulted</see>, or
269         /// <see cref="System.Threading.Tasks.TaskStatus.Canceled">Canceled</see>.
270         /// </remarks>
271         /// <exception cref="T:System.ObjectDisposedException">The <see cref="Task"/> was disposed.</exception>
TrySetResult(TResult result)272         public bool TrySetResult(TResult result)
273         {
274             bool rval = _task.TrySetResult(result);
275             if (!rval) SpinUntilCompleted();
276             return rval;
277         }
278 
279         /// <summary>
280         /// Transitions the underlying
281         /// <see cref="T:System.Threading.Tasks.Task{TResult}"/> into the
282         /// <see cref="System.Threading.Tasks.TaskStatus.RanToCompletion">RanToCompletion</see>
283         /// state.
284         /// </summary>
285         /// <param name="result">The result value to bind to this <see
286         /// cref="T:System.Threading.Tasks.Task{TResult}"/>.</param>
287         /// <exception cref="T:System.InvalidOperationException">
288         /// The underlying <see cref="T:System.Threading.Tasks.Task{TResult}"/> is already in one
289         /// of the three final states:
290         /// <see cref="System.Threading.Tasks.TaskStatus.RanToCompletion">RanToCompletion</see>,
291         /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">Faulted</see>, or
292         /// <see cref="System.Threading.Tasks.TaskStatus.Canceled">Canceled</see>.
293         /// </exception>
294         /// <exception cref="T:System.ObjectDisposedException">The <see cref="Task"/> was disposed.</exception>
SetResult(TResult result)295         public void SetResult(TResult result)
296         {
297             if (!TrySetResult(result))
298                 ThrowHelper.ThrowInvalidOperationException(ExceptionResource.TaskT_TransitionToFinal_AlreadyCompleted);
299         }
300 
301         /// <summary>
302         /// Attempts to transition the underlying
303         /// <see cref="T:System.Threading.Tasks.Task{TResult}"/> into the
304         /// <see cref="System.Threading.Tasks.TaskStatus.Canceled">Canceled</see>
305         /// state.
306         /// </summary>
307         /// <returns>True if the operation was successful; otherwise, false.</returns>
308         /// <remarks>This operation will return false if the
309         /// <see cref="T:System.Threading.Tasks.Task{TResult}"/> is already in one
310         /// of the three final states:
311         /// <see cref="System.Threading.Tasks.TaskStatus.RanToCompletion">RanToCompletion</see>,
312         /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">Faulted</see>, or
313         /// <see cref="System.Threading.Tasks.TaskStatus.Canceled">Canceled</see>.
314         /// </remarks>
315         /// <exception cref="T:System.ObjectDisposedException">The <see cref="Task"/> was disposed.</exception>
TrySetCanceled()316         public bool TrySetCanceled()
317         {
318             return TrySetCanceled(default(CancellationToken));
319         }
320 
321         // Enables a token to be stored into the canceled task
TrySetCanceled(CancellationToken cancellationToken)322         public bool TrySetCanceled(CancellationToken cancellationToken)
323         {
324             bool rval = _task.TrySetCanceled(cancellationToken);
325             if (!rval && !_task.IsCompleted) SpinUntilCompleted();
326             return rval;
327         }
328 
329         /// <summary>
330         /// Transitions the underlying
331         /// <see cref="T:System.Threading.Tasks.Task{TResult}"/> into the
332         /// <see cref="System.Threading.Tasks.TaskStatus.Canceled">Canceled</see>
333         /// state.
334         /// </summary>
335         /// <exception cref="T:System.InvalidOperationException">
336         /// The underlying <see cref="T:System.Threading.Tasks.Task{TResult}"/> is already in one
337         /// of the three final states:
338         /// <see cref="System.Threading.Tasks.TaskStatus.RanToCompletion">RanToCompletion</see>,
339         /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">Faulted</see>, or
340         /// <see cref="System.Threading.Tasks.TaskStatus.Canceled">Canceled</see>.
341         /// </exception>
342         /// <exception cref="T:System.ObjectDisposedException">The <see cref="Task"/> was disposed.</exception>
SetCanceled()343         public void SetCanceled()
344         {
345             if (!TrySetCanceled())
346                 ThrowHelper.ThrowInvalidOperationException(ExceptionResource.TaskT_TransitionToFinal_AlreadyCompleted);
347         }
348     }
349 }
350