/// <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)));
 }
Пример #2
0
        /// <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);
        }
Пример #4
0
        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());
        }