|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.ComponentModel;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
|
|
|
|
namespace ZeroLevel.Services.Async
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Provides extension methods for <see cref="TaskCompletionSource{TResult}"/>.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public static class TaskCompletionSourceExtensions
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Attempts to complete a <see cref="TaskCompletionSource{TResult}"/>, propagating the completion of <paramref name="task"/>.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <typeparam name="TResult">The type of the result of the target asynchronous operation.</typeparam>
|
|
|
|
|
/// <typeparam name="TSourceResult">The type of the result of the source asynchronous operation.</typeparam>
|
|
|
|
|
/// <param name="this">The task completion source. May not be <c>null</c>.</param>
|
|
|
|
|
/// <param name="task">The task. May not be <c>null</c>.</param>
|
|
|
|
|
/// <returns><c>true</c> if this method completed the task completion source; <c>false</c> if it was already completed.</returns>
|
|
|
|
|
public static bool TryCompleteFromCompletedTask<TResult, TSourceResult>(this TaskCompletionSource<TResult> @this, Task<TSourceResult> task) where TSourceResult : TResult
|
|
|
|
|
{
|
|
|
|
|
if (@this == null)
|
|
|
|
|
throw new ArgumentNullException(nameof(@this));
|
|
|
|
|
if (task == null)
|
|
|
|
|
throw new ArgumentNullException(nameof(task));
|
|
|
|
|
|
|
|
|
|
if (task.IsFaulted)
|
|
|
|
|
return @this.TrySetException(task.Exception.InnerExceptions);
|
|
|
|
|
if (task.IsCanceled)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
task.WaitAndUnwrapException();
|
|
|
|
|
}
|
|
|
|
|
catch (OperationCanceledException exception)
|
|
|
|
|
{
|
|
|
|
|
var token = exception.CancellationToken;
|
|
|
|
|
return token.IsCancellationRequested ? @this.TrySetCanceled(token) : @this.TrySetCanceled();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return @this.TrySetResult(task.Result);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Attempts to complete a <see cref="TaskCompletionSource{TResult}"/>, propagating the completion of <paramref name="task"/> but using the result value from <paramref name="resultFunc"/> if the task completed successfully.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <typeparam name="TResult">The type of the result of the target asynchronous operation.</typeparam>
|
|
|
|
|
/// <param name="this">The task completion source. May not be <c>null</c>.</param>
|
|
|
|
|
/// <param name="task">The task. May not be <c>null</c>.</param>
|
|
|
|
|
/// <param name="resultFunc">A delegate that returns the result with which to complete the task completion source, if the task completed successfully. May not be <c>null</c>.</param>
|
|
|
|
|
/// <returns><c>true</c> if this method completed the task completion source; <c>false</c> if it was already completed.</returns>
|
|
|
|
|
public static bool TryCompleteFromCompletedTask<TResult>(this TaskCompletionSource<TResult> @this, Task task, Func<TResult> resultFunc)
|
|
|
|
|
{
|
|
|
|
|
if (@this == null)
|
|
|
|
|
throw new ArgumentNullException(nameof(@this));
|
|
|
|
|
if (task == null)
|
|
|
|
|
throw new ArgumentNullException(nameof(task));
|
|
|
|
|
if (resultFunc == null)
|
|
|
|
|
throw new ArgumentNullException(nameof(resultFunc));
|
|
|
|
|
|
|
|
|
|
if (task.IsFaulted)
|
|
|
|
|
return @this.TrySetException(task.Exception.InnerExceptions);
|
|
|
|
|
if (task.IsCanceled)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
task.WaitAndUnwrapException();
|
|
|
|
|
}
|
|
|
|
|
catch (OperationCanceledException exception)
|
|
|
|
|
{
|
|
|
|
|
var token = exception.CancellationToken;
|
|
|
|
|
return token.IsCancellationRequested ? @this.TrySetCanceled(token) : @this.TrySetCanceled();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return @this.TrySetResult(resultFunc());
|
|
|
|
|
}
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Attempts to complete a <see cref="TaskCompletionSource{TResult}"/>, propagating the completion of <paramref name="eventArgs"/>.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <typeparam name="TResult">The type of the result of the asynchronous operation.</typeparam>
|
|
|
|
|
/// <param name="this">The task completion source. May not be <c>null</c>.</param>
|
|
|
|
|
/// <param name="eventArgs">The event arguments passed to the completion event. May not be <c>null</c>.</param>
|
|
|
|
|
/// <param name="getResult">The delegate used to retrieve the result. This is only invoked if <paramref name="eventArgs"/> indicates successful completion. May not be <c>null</c>.</param>
|
|
|
|
|
/// <returns><c>true</c> if this method completed the task completion source; <c>false</c> if it was already completed.</returns>
|
|
|
|
|
public static bool TryCompleteFromEventArgs<TResult>(this TaskCompletionSource<TResult> @this, AsyncCompletedEventArgs eventArgs, Func<TResult> getResult)
|
|
|
|
|
{
|
|
|
|
|
if (eventArgs.Cancelled)
|
|
|
|
|
return @this.TrySetCanceled();
|
|
|
|
|
if (eventArgs.Error != null)
|
|
|
|
|
return @this.TrySetException(eventArgs.Error);
|
|
|
|
|
return @this.TrySetResult(getResult());
|
|
|
|
|
}
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Attempts to complete a <see cref="TaskCompletionSource"/>, propagating the completion of <paramref name="task"/>.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="this">The task completion source. May not be <c>null</c>.</param>
|
|
|
|
|
/// <param name="task">The task. May not be <c>null</c>.</param>
|
|
|
|
|
/// <returns><c>true</c> if this method completed the task completion source; <c>false</c> if it was already completed.</returns>
|
|
|
|
|
public static bool TryCompleteFromCompletedTask(this TaskCompletionSource @this, Task task)
|
|
|
|
|
{
|
|
|
|
|
if (task.IsFaulted)
|
|
|
|
|
return @this.TrySetException(task.Exception.InnerExceptions);
|
|
|
|
|
if (task.IsCanceled)
|
|
|
|
|
return @this.TrySetCanceled();
|
|
|
|
|
return @this.TrySetResult();
|
|
|
|
|
}
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Attempts to complete a <see cref="TaskCompletionSource"/>, propagating the completion of <paramref name="eventArgs"/>.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="this">The task completion source. May not be <c>null</c>.</param>
|
|
|
|
|
/// <param name="eventArgs">The event arguments passed to the completion event. May not be <c>null</c>.</param>
|
|
|
|
|
/// <returns><c>true</c> if this method completed the task completion source; <c>false</c> if it was already completed.</returns>
|
|
|
|
|
public static bool TryCompleteFromEventArgs(this TaskCompletionSource @this, AsyncCompletedEventArgs eventArgs)
|
|
|
|
|
{
|
|
|
|
|
if (eventArgs.Cancelled)
|
|
|
|
|
return @this.TrySetCanceled();
|
|
|
|
|
if (eventArgs.Error != null)
|
|
|
|
|
return @this.TrySetException(eventArgs.Error);
|
|
|
|
|
return @this.TrySetResult();
|
|
|
|
|
}
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Attempts to complete a <see cref="TaskCompletionSource{TResult}"/> with the specified value, forcing all continuations onto a threadpool thread even if they specified <c>ExecuteSynchronously</c>.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <typeparam name="TResult">The type of the result of the asynchronous operation.</typeparam>
|
|
|
|
|
/// <param name="this">The task completion source. May not be <c>null</c>.</param>
|
|
|
|
|
/// <param name="result">The result of the asynchronous operation.</param>
|
|
|
|
|
public static void TrySetResultWithBackgroundContinuations<TResult>(this TaskCompletionSource<TResult> @this, TResult result)
|
|
|
|
|
{
|
|
|
|
|
// Set the result on a threadpool thread, so any synchronous continuations will execute in the background.
|
|
|
|
|
TaskShim.Run(() => @this.TrySetResult(result));
|
|
|
|
|
|
|
|
|
|
// Wait for the TCS task to complete; note that the continuations may not be complete.
|
|
|
|
|
@this.Task.Wait();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Attempts to complete a <see cref="TaskCompletionSource"/>, forcing all continuations onto a threadpool thread even if they specified <c>ExecuteSynchronously</c>.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="this">The task completion source. May not be <c>null</c>.</param>
|
|
|
|
|
public static void TrySetResultWithBackgroundContinuations(this TaskCompletionSource @this)
|
|
|
|
|
{
|
|
|
|
|
// Set the result on a threadpool thread, so any synchronous continuations will execute in the background.
|
|
|
|
|
TaskShim.Run(() => @this.TrySetResult());
|
|
|
|
|
|
|
|
|
|
// Wait for the TCS task to complete; note that the continuations may not be complete.
|
|
|
|
|
@this.Task.Wait();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Attempts to complete a <see cref="TaskCompletionSource{TResult}"/> as canceled, forcing all continuations onto a threadpool thread even if they specified <c>ExecuteSynchronously</c>.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <typeparam name="TResult">The type of the result of the asynchronous operation.</typeparam>
|
|
|
|
|
/// <param name="this">The task completion source. May not be <c>null</c>.</param>
|
|
|
|
|
public static void TrySetCanceledWithBackgroundContinuations<TResult>(this TaskCompletionSource<TResult> @this)
|
|
|
|
|
{
|
|
|
|
|
// Complete on a threadpool thread, so any synchronous continuations will execute in the background.
|
|
|
|
|
TaskShim.Run(() => @this.TrySetCanceled());
|
|
|
|
|
|
|
|
|
|
// Wait for the TCS task to complete; note that the continuations may not be complete.
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
@this.Task.Wait();
|
|
|
|
|
}
|
|
|
|
|
catch (AggregateException)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Creates a new TCS for use with async code, and which forces its continuations to execute asynchronously.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <typeparam name="TResult">The type of the result of the TCS.</typeparam>
|
|
|
|
|
public static TaskCompletionSource<TResult> CreateAsyncTaskSource<TResult>()
|
|
|
|
|
{
|
|
|
|
|
return new TaskCompletionSource<TResult>(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
|
|
|
}
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Attempts to complete a <see cref="TaskCompletionSource"/> as canceled, forcing all continuations onto a threadpool thread even if they specified <c>ExecuteSynchronously</c>.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="this">The task completion source. May not be <c>null</c>.</param>
|
|
|
|
|
public static void TrySetCanceledWithBackgroundContinuations(this TaskCompletionSource @this)
|
|
|
|
|
{
|
|
|
|
|
// Set the result on a threadpool thread, so any synchronous continuations will execute in the background.
|
|
|
|
|
TaskShim.Run(() => @this.TrySetCanceled());
|
|
|
|
|
|
|
|
|
|
// Wait for the TCS task to complete; note that the continuations may not be complete.
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
@this.Task.Wait();
|
|
|
|
|
}
|
|
|
|
|
catch (AggregateException)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Attempts to complete a <see cref="TaskCompletionSource{TResult}"/> as faulted, forcing all continuations onto a threadpool thread even if they specified <c>ExecuteSynchronously</c>.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <typeparam name="TResult">The type of the result of the asynchronous operation.</typeparam>
|
|
|
|
|
/// <param name="this">The task completion source. May not be <c>null</c>.</param>
|
|
|
|
|
/// <param name="exception">The exception to bind to the task.</param>
|
|
|
|
|
public static void TrySetExceptionWithBackgroundContinuations<TResult>(this TaskCompletionSource<TResult> @this, Exception exception)
|
|
|
|
|
{
|
|
|
|
|
// Complete on a threadpool thread, so any synchronous continuations will execute in the background.
|
|
|
|
|
TaskShim.Run(() => @this.TrySetException(exception));
|
|
|
|
|
|
|
|
|
|
// Wait for the TCS task to complete; note that the continuations may not be complete.
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
@this.Task.Wait();
|
|
|
|
|
}
|
|
|
|
|
catch (AggregateException)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Attempts to complete a <see cref="TaskCompletionSource"/> as faulted, forcing all continuations onto a threadpool thread even if they specified <c>ExecuteSynchronously</c>.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="this">The task completion source. May not be <c>null</c>.</param>
|
|
|
|
|
/// <param name="exception">The exception to bind to the task.</param>
|
|
|
|
|
public static void TrySetExceptionWithBackgroundContinuations(this TaskCompletionSource @this, Exception exception)
|
|
|
|
|
{
|
|
|
|
|
// Set the result on a threadpool thread, so any synchronous continuations will execute in the background.
|
|
|
|
|
TaskShim.Run(() => @this.TrySetException(exception));
|
|
|
|
|
|
|
|
|
|
// Wait for the TCS task to complete; note that the continuations may not be complete.
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
@this.Task.Wait();
|
|
|
|
|
}
|
|
|
|
|
catch (AggregateException)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Attempts to complete a <see cref="TaskCompletionSource{TResult}"/> as faulted, forcing all continuations onto a threadpool thread even if they specified <c>ExecuteSynchronously</c>.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <typeparam name="TResult">The type of the result of the asynchronous operation.</typeparam>
|
|
|
|
|
/// <param name="this">The task completion source. May not be <c>null</c>.</param>
|
|
|
|
|
/// <param name="exceptions">The exceptions to bind to the task.</param>
|
|
|
|
|
public static void TrySetExceptionWithBackgroundContinuations<TResult>(this TaskCompletionSource<TResult> @this, IEnumerable<Exception> exceptions)
|
|
|
|
|
{
|
|
|
|
|
// Complete on a threadpool thread, so any synchronous continuations will execute in the background.
|
|
|
|
|
TaskShim.Run(() => @this.TrySetException(exceptions));
|
|
|
|
|
|
|
|
|
|
// Wait for the TCS task to complete; note that the continuations may not be complete.
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
@this.Task.Wait();
|
|
|
|
|
}
|
|
|
|
|
catch (AggregateException)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Attempts to complete a <see cref="TaskCompletionSource"/> as faulted, forcing all continuations onto a threadpool thread even if they specified <c>ExecuteSynchronously</c>.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="this">The task completion source. May not be <c>null</c>.</param>
|
|
|
|
|
/// <param name="exceptions">The exceptions to bind to the task.</param>
|
|
|
|
|
public static void TrySetExceptionWithBackgroundContinuations(this TaskCompletionSource @this, IEnumerable<Exception> exceptions)
|
|
|
|
|
{
|
|
|
|
|
// Set the result on a threadpool thread, so any synchronous continuations will execute in the background.
|
|
|
|
|
TaskShim.Run(() => @this.TrySetException(exceptions));
|
|
|
|
|
|
|
|
|
|
// Wait for the TCS task to complete; note that the continuations may not be complete.
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
@this.Task.Wait();
|
|
|
|
|
}
|
|
|
|
|
catch (AggregateException)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|