/// <summary>Attempts to transfer the result of a Task to the TaskCompletionSource.</summary> /// <typeparam name="TResult">Specifies the type of the result.</typeparam> /// <param name="resultSetter">The TaskCompletionSource.</param> /// <param name="task">The task whose completion results should be transfered.</param> /// <returns>Whether the transfer could be completed.</returns> public static bool TrySetFromTask <TResult>(this TaskCompletionSource <TResult> resultSetter, Task <TResult> task) { return(resultSetter.TrySetFromTask <TResult>(((Task)task))); }
/// <summary> /// Creates a proxy <see cref="System.Threading.Tasks.Task">Task</see> that represents the /// asynchronous operation of a Task{Task}. /// </summary> /// <remarks> /// It is often useful to be able to return a Task from a <see cref="System.Threading.Tasks.Task{TResult}"> /// Task{TResult}</see>, where the inner Task represents work done as part of the outer Task{TResult}. However, /// doing so results in a Task{Task}, which, if not dealt with carefully, could produce unexpected behavior. Unwrap /// solves this problem by creating a proxy Task that represents the entire asynchronous operation of such a Task{Task}. /// </remarks> /// <param name="task">The Task{Task} to unwrap.</param> /// <exception cref="T:System.ArgumentNullException">The exception that is thrown if the /// <paramref name="task"/> argument is null.</exception> /// <returns>A Task that represents the asynchronous operation of the provided Task{Task}.</returns> public static Task Unwrap(this Task <Task> task) { if (task == null) { throw new ArgumentNullException("task"); } #if SILVERLIGHT && !FEATURE_NETCORE // CoreCLR only bool result; // tcs.Task serves as a proxy for task.Result. // AttachedToParent is the only legal option for TCS-style task. var tcs = new TaskCompletionSource <Task>(task.CreationOptions & TaskCreationOptions.AttachedToParent); // Set up some actions to take when task has completed. task.ContinueWith(delegate { switch (task.Status) { // If task did not run to completion, then record the cancellation/fault information // to tcs.Task. case TaskStatus.Canceled: case TaskStatus.Faulted: result = tcs.TrySetFromTask(task); Contract.Assert(result, "Unwrap(Task<Task>): Expected TrySetFromTask #1 to succeed"); break; case TaskStatus.RanToCompletion: // task.Result == null ==> proxy should be canceled. if (task.Result == null) { tcs.TrySetCanceled(); } // When task.Result completes, take some action to set the completion state of tcs.Task. else { task.Result.ContinueWith(_ => { // Copy completion/cancellation/exception info from task.Result to tcs.Task. result = tcs.TrySetFromTask(task.Result); Contract.Assert(result, "Unwrap(Task<Task>): Expected TrySetFromTask #2 to succeed"); }, TaskContinuationOptions.ExecuteSynchronously).ContinueWith(antecedent => { // Clean up if ContinueWith() operation fails due to TSE tcs.TrySetException(antecedent.Exception); }, TaskContinuationOptions.OnlyOnFaulted); } break; } }, TaskContinuationOptions.ExecuteSynchronously).ContinueWith(antecedent => { // Clean up if ContinueWith() operation fails due to TSE tcs.TrySetException(antecedent.Exception); }, TaskContinuationOptions.OnlyOnFaulted); // Return this immediately as a proxy. When task.Result completes, or task is faulted/canceled, // the completion information will be transfered to tcs.Task. return(tcs.Task); #else // Desktop or CoreSys // Creates a proxy Task and hooks up the logic to have it represent the task.Result Task promise = Task.CreateUnwrapPromise <VoidResult>(task, lookForOce: false); // Return the proxy immediately return(promise); #endif }
/// <summary> /// Transfer the results of the <paramref name="outer"/> task's inner task to the <paramref name="completionSource"/>. /// </summary> /// <param name="completionSource">The completion source to which results should be transfered.</param> /// <param name="outer"> /// The outer task that when completed will yield an inner task whose results we want marshaled to the <paramref name="completionSource"/>. /// </param> private static void TransferAsynchronously <TResult, TInner>(TaskCompletionSource <TResult> completionSource, Task <TInner> outer) where TInner : Task { Action callback = null; // Create a continuation delegate. For performance reasons, we reuse the same delegate/closure across multiple // continuations; by using .ConfigureAwait(false).GetAwaiter().UnsafeOnComplete(action), in most cases // this delegate can be stored directly into the Task's continuation field, eliminating the need for additional // allocations. Thus, this whole Unwrap operation generally results in four allocations: one for the TaskCompletionSource, // one for the returned task, one for the delegate, and one for the closure. Since the delegate is used // across multiple continuations, we use the callback variable as well to indicate which continuation we're in: // if the callback is non-null, then we're processing the continuation for the outer task and use the callback // object as the continuation off of the inner task; if the callback is null, then we're processing the // inner task. callback = delegate { Debug.Assert(outer.IsCompleted); if (callback != null) { // Process the outer task's completion // Clear out the callback field to indicate that any future invocations should // be for processing the inner task, but store away a local copy in case we need // to use it as the continuation off of the outer task. Action innerCallback = callback; callback = null; bool result = true; switch (outer.Status) { case TaskStatus.Canceled: case TaskStatus.Faulted: // The outer task has completed as canceled or faulted; transfer that // status to the completion source, and we're done. result = completionSource.TrySetFromTask(outer); break; case TaskStatus.RanToCompletion: Task inner = outer.Result; if (inner == null) { // The outer task completed successfully, but with a null inner task; // cancel the completionSource, and we're done. result = completionSource.TrySetCanceled(); } else if (inner.IsCompleted) { // The inner task also completed! Transfer the results, and we're done. result = completionSource.TrySetFromTask(inner); } else { // Run this delegate again once the inner task has completed. inner.ConfigureAwait(false).GetAwaiter().UnsafeOnCompleted(innerCallback); } break; } Debug.Assert(result); } else { // Process the inner task's completion. All we need to do is transfer its results // to the completion source. Debug.Assert(outer.Status == TaskStatus.RanToCompletion); Debug.Assert(outer.Result.IsCompleted); completionSource.TrySetFromTask(outer.Result); } }; // Kick things off by hooking up the callback as the task's continuation outer.ConfigureAwait(false).GetAwaiter().UnsafeOnCompleted(callback); }
private static Task <TResult> CatchImplContinuation <TResult>(Task task, Func <Task <TResult> > continuation) { SynchronizationContext syncContext = SynchronizationContext.Current; TaskCompletionSource <Task <TResult> > tcs = new TaskCompletionSource <Task <TResult> >(); // this runs only if the inner task did not fault task.ContinueWith(innerTask => { if (syncContext != null) { syncContext.Post(state => { tcs.TrySetFromTask(innerTask); }, state: null); } else { tcs.TrySetFromTask(innerTask); } } , TaskContinuationOptions.NotOnFaulted | TaskContinuationOptions.ExecuteSynchronously); // this runs only if the inner task faulted task.ContinueWith(innerTask => { if (syncContext != null) { syncContext.Post(state => { try { Task <TResult> resultTask = continuation(); if (resultTask == null) { throw new InvalidOperationException("You cannot return null from the TaskHelpersExtensions.Catch continuation. You must return a valid task or throw an exception."); } tcs.TrySetResult(resultTask); } catch (Exception ex) { tcs.TrySetException(ex); } }, state: null); } else { try { Task <TResult> resultTask = continuation(); if (resultTask == null) { throw new InvalidOperationException("You cannot return null from the TaskHelpersExtensions.Catch continuation. You must return a valid task or throw an exception."); } tcs.TrySetResult(resultTask); } catch (Exception ex) { tcs.TrySetException(ex); } } }, TaskContinuationOptions.OnlyOnFaulted); return(tcs.Task.FastUnwrap()); }