/// <summary>Gets a Task to represent the asynchronous operation.</summary> /// <param name="source">The asynchronous operation.</param> /// <param name="cancellationToken">The token used to request cancellation of the asynchronous operation.</param> /// <returns>The Task representing the asynchronous operation.</returns> public static Task AsTask(this IAsyncAction source, CancellationToken cancellationToken) { if (source == null) { throw new ArgumentNullException(nameof(source)); } Contract.EndContractBlock(); // If source is actually a NetFx-to-WinRT adapter, unwrap it instead of creating a new Task: var wrapper = source as TaskToAsyncActionAdapter; if (wrapper != null && !wrapper.CompletedSynchronously) { Task innerTask = wrapper.Task; Debug.Assert(innerTask != null); Debug.Assert(innerTask.Status != TaskStatus.Created); if (!innerTask.IsCompleted) { // The race here is benign: If the task completes here, the concatination is useless, but not damaging. if (cancellationToken.CanBeCanceled && wrapper.CancelTokenSource != null) { ConcatenateCancelTokens(cancellationToken, wrapper.CancelTokenSource, innerTask); } } return(innerTask); } // Fast path to return a completed Task if the operation has already completed: switch (source.Status) { case AsyncStatus.Completed: return(Task.CompletedTask); case AsyncStatus.Error: return(Task.FromException(source.ErrorCode.AttachRestrictedErrorInfo())); case AsyncStatus.Canceled: return(Task.FromCanceled(cancellationToken.IsCancellationRequested ? cancellationToken : new CancellationToken(true))); } // Benign race: source may complete here. Things still work, just not taking the fast path. // Source is not a NetFx-to-WinRT adapter, but a native future. Hook up the task: var bridge = new AsyncInfoToTaskBridge <VoidValueTypeParameter>(cancellationToken); try { source.Completed = new AsyncActionCompletedHandler(bridge.CompleteFromAsyncAction); bridge.RegisterForCancellation(source); } catch { AsyncCausalitySupport.RemoveFromActiveTasks(bridge.Task); } return(bridge.Task); }
internal AsyncInfoToTaskBridge(CancellationToken cancellationToken) { if (AsyncCausalitySupport.LoggingOn) AsyncCausalitySupport.TraceOperationCreation(this.Task, "WinRT Operation as Task"); AsyncCausalitySupport.AddToActiveTasks(this.Task); _ct = cancellationToken; }
/// <summary>Completes the task from the completed asynchronous operation.</summary> /// <param name="asyncInfo">The asynchronous operation.</param> /// <param name="getResultsFunction">A function used to retrieve the TResult from the async operation; may be null.</param> /// <param name="asyncStatus">The status of the asynchronous operation.</param> private void Complete(IAsyncInfo asyncInfo, Func <IAsyncInfo, TResult> getResultsFunction, AsyncStatus asyncStatus) { if (asyncInfo == null) { throw new ArgumentNullException(nameof(asyncInfo)); } Contract.EndContractBlock(); AsyncCausalitySupport.RemoveFromActiveTasks(this.Task); try { Debug.Assert(asyncInfo.Status == asyncStatus, "asyncInfo.Status does not match asyncStatus; are we dealing with a faulty IAsyncInfo implementation?"); // Assuming a correct underlying implementation, the task should not have been // completed yet. If it is completed, we shouldn't try to do any further work // with the operation or the task, as something is horked. bool taskAlreadyCompleted = Task.IsCompleted; Debug.Assert(!taskAlreadyCompleted, "Expected the task to not yet be completed."); if (taskAlreadyCompleted) { throw new InvalidOperationException(SR.InvalidOperation_InvalidAsyncCompletion); } // Clean up our registration with the cancellation token, noting that we're now in the process of cleaning up. CancellationTokenRegistration ctr; lock (StateLock) { _completing = true; ctr = _ctr; // under lock to avoid torn reads _ctr = default(CancellationTokenRegistration); } ctr.TryDeregister(); // It's ok if we end up unregistering a not-initialized registration; it'll just be a nop. try { // Find out how the async operation completed. It must be in a terminal state. bool terminalState = asyncStatus == AsyncStatus.Completed || asyncStatus == AsyncStatus.Canceled || asyncStatus == AsyncStatus.Error; Debug.Assert(terminalState, "The async operation should be in a terminal state."); if (!terminalState) { throw new InvalidOperationException(SR.InvalidOperation_InvalidAsyncCompletion); } // Retrieve the completion data from the IAsyncInfo. TResult result = default(TResult); Exception error = null; if (asyncStatus == AsyncStatus.Error) { error = asyncInfo.ErrorCode; // Defend against a faulty IAsyncInfo implementation: if (error == null) { Debug.Assert(false, "IAsyncInfo.Status == Error, but ErrorCode returns a null Exception (implying S_OK)."); error = new InvalidOperationException(SR.InvalidOperation_InvalidAsyncCompletion); } else { error = asyncInfo.ErrorCode.AttachRestrictedErrorInfo(); } } else if (asyncStatus == AsyncStatus.Completed && getResultsFunction != null) { try { result = getResultsFunction(asyncInfo); } catch (Exception resultsEx) { // According to the WinRT team, this can happen in some egde cases, such as marshalling errors in GetResults. error = resultsEx; asyncStatus = AsyncStatus.Error; } } // Nothing to retrieve for a canceled operation or for a completed operation with no result. // Complete the task based on the previously retrieved results: bool success = false; switch (asyncStatus) { case AsyncStatus.Completed: if (AsyncCausalitySupport.LoggingOn) { AsyncCausalitySupport.TraceOperationCompletedSuccess(this.Task); } success = base.TrySetResult(result); break; case AsyncStatus.Error: Debug.Assert(error != null, "The error should have been retrieved previously."); success = base.TrySetException(error); break; case AsyncStatus.Canceled: success = base.TrySetCanceled(_ct.IsCancellationRequested ? _ct : new CancellationToken(true)); break; } Debug.Assert(success, "Expected the outcome to be successfully transfered to the task."); } catch (Exception exc) { // This really shouldn't happen, but could in a variety of misuse cases // such as a faulty underlying IAsyncInfo implementation. Debug.Assert(false, string.Format("Unexpected exception in Complete: {0}", exc.ToString())); if (AsyncCausalitySupport.LoggingOn) { AsyncCausalitySupport.TraceOperationCompletedError(this.Task); } // For these cases, store the exception into the task so that it makes its way // back to the caller. Only if something went horribly wrong and we can't store the exception // do we allow it to be propagated out to the invoker of the Completed handler. if (!base.TrySetException(exc)) { Debug.Assert(false, "The task was already completed and thus the exception couldn't be stored."); throw; } } } finally { // We may be called on an STA thread which we don't own, so make sure that the RCW is released right // away. Otherwise, if we leave it up to the finalizer, the apartment may already be gone. if (Marshal.IsComObject(asyncInfo)) { Marshal.ReleaseComObject(asyncInfo); } } } // private void Complete(..)
/// <summary>Gets a Task to represent the asynchronous operation.</summary> /// <param name="source">The asynchronous operation.</param> /// <param name="cancellationToken">The token used to request cancellation of the asynchronous operation.</param> /// <param name="progress">The progress object used to receive progress updates.</param> /// <returns>The Task representing the asynchronous operation.</returns> public static Task <TResult> AsTask <TResult, TProgress>(this IAsyncOperationWithProgress <TResult, TProgress> source, CancellationToken cancellationToken, IProgress <TProgress> progress) { if (source == null) { throw new ArgumentNullException(nameof(source)); } Contract.EndContractBlock(); // If source is actually a NetFx-to-WinRT adapter, unwrap it instead of creating a new Task: var wrapper = source as TaskToAsyncOperationWithProgressAdapter <TResult, TProgress>; if (wrapper != null && !wrapper.CompletedSynchronously) { Task <TResult> innerTask = wrapper.Task as Task <TResult>; Debug.Assert(innerTask != null); Debug.Assert(innerTask.Status != TaskStatus.Created); // Is WaitingForActivation a legal state at this moment? if (!innerTask.IsCompleted) { // The race here is benign: If the task completes here, the concatinations are useless, but not damaging. if (cancellationToken.CanBeCanceled && wrapper.CancelTokenSource != null) { ConcatenateCancelTokens(cancellationToken, wrapper.CancelTokenSource, innerTask); } if (progress != null) { ConcatenateProgress(source, progress); } } return(innerTask); } // Fast path to return a completed Task if the operation has already completed switch (source.Status) { case AsyncStatus.Completed: return(Task.FromResult(source.GetResults())); case AsyncStatus.Error: return(Task.FromException <TResult>(source.ErrorCode.AttachRestrictedErrorInfo())); case AsyncStatus.Canceled: return(Task.FromCanceled <TResult>(cancellationToken.IsCancellationRequested ? cancellationToken : new CancellationToken(true))); } // Benign race: source may complete here. Things still work, just not taking the fast path. // Forward progress reports: if (progress != null) { ConcatenateProgress(source, progress); } // Source is not a NetFx-to-WinRT adapter, but a native future. Hook up the task: var bridge = new AsyncInfoToTaskBridge <TResult>(cancellationToken); try { source.Completed = new AsyncOperationWithProgressCompletedHandler <TResult, TProgress>(bridge.CompleteFromAsyncOperationWithProgress); bridge.RegisterForCancellation(source); } catch { AsyncCausalitySupport.RemoveFromActiveTasks(bridge.Task); } return(bridge.Task); }