|
|
|
|
using System;
|
|
|
|
|
using System.Threading;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
|
|
|
|
namespace ZeroLevel.Services.Async
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Provides synchronous extension methods for tasks.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public static class TaskExtensions
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Asynchronously waits for the task to complete, or for the cancellation token to be canceled.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="this">The task to wait for. May not be <c>null</c>.</param>
|
|
|
|
|
/// <param name="cancellationToken">The cancellation token that cancels the wait.</param>
|
|
|
|
|
public static Task WaitAsync(this Task @this, CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
if (@this == null)
|
|
|
|
|
throw new ArgumentNullException(nameof(@this));
|
|
|
|
|
if (!cancellationToken.CanBeCanceled)
|
|
|
|
|
return @this;
|
|
|
|
|
if (cancellationToken.IsCancellationRequested)
|
|
|
|
|
return Task.FromCanceled(cancellationToken);
|
|
|
|
|
return DoWaitAsync(@this, cancellationToken);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static async Task DoWaitAsync(Task task, CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
using (var cancelTaskSource = new CancellationTokenTaskSource<object>(cancellationToken))
|
|
|
|
|
await await Task.WhenAny(task, cancelTaskSource.Task).ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Asynchronously waits for the task to complete, or for the cancellation token to be canceled.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <typeparam name="TResult">The type of the task result.</typeparam>
|
|
|
|
|
/// <param name="this">The task to wait for. May not be <c>null</c>.</param>
|
|
|
|
|
/// <param name="cancellationToken">The cancellation token that cancels the wait.</param>
|
|
|
|
|
public static Task<TResult> WaitAsync<TResult>(this Task<TResult> @this, CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
if (@this == null)
|
|
|
|
|
throw new ArgumentNullException(nameof(@this));
|
|
|
|
|
|
|
|
|
|
if (!cancellationToken.CanBeCanceled)
|
|
|
|
|
return @this;
|
|
|
|
|
if (cancellationToken.IsCancellationRequested)
|
|
|
|
|
return Task.FromCanceled<TResult>(cancellationToken);
|
|
|
|
|
return DoWaitAsync(@this, cancellationToken);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static async Task<TResult> DoWaitAsync<TResult>(Task<TResult> task, CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
using (var cancelTaskSource = new CancellationTokenTaskSource<TResult>(cancellationToken))
|
|
|
|
|
return await await Task.WhenAny(task, cancelTaskSource.Task).ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Waits for the task to complete, unwrapping any exceptions.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="task">The task. May not be <c>null</c>.</param>
|
|
|
|
|
public static void WaitAndUnwrapException(this Task task)
|
|
|
|
|
{
|
|
|
|
|
task.GetAwaiter().GetResult();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Waits for the task to complete, unwrapping any exceptions.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="task">The task. May not be <c>null</c>.</param>
|
|
|
|
|
/// <param name="cancellationToken">A cancellation token to observe while waiting for the task to complete.</param>
|
|
|
|
|
/// <exception cref="OperationCanceledException">The <paramref name="cancellationToken"/> was cancelled before the <paramref name="task"/> completed, or the <paramref name="task"/> raised an <see cref="OperationCanceledException"/>.</exception>
|
|
|
|
|
public static void WaitAndUnwrapException(this Task task, CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
task.Wait(cancellationToken);
|
|
|
|
|
}
|
|
|
|
|
catch (AggregateException ex)
|
|
|
|
|
{
|
|
|
|
|
throw ExceptionHelpers.PrepareForRethrow(ex.InnerException);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Waits for the task to complete, unwrapping any exceptions.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <typeparam name="TResult">The type of the result of the task.</typeparam>
|
|
|
|
|
/// <param name="task">The task. May not be <c>null</c>.</param>
|
|
|
|
|
/// <returns>The result of the task.</returns>
|
|
|
|
|
public static TResult WaitAndUnwrapException<TResult>(this Task<TResult> task)
|
|
|
|
|
{
|
|
|
|
|
return task.GetAwaiter().GetResult();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Waits for the task to complete, unwrapping any exceptions.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <typeparam name="TResult">The type of the result of the task.</typeparam>
|
|
|
|
|
/// <param name="task">The task. May not be <c>null</c>.</param>
|
|
|
|
|
/// <param name="cancellationToken">A cancellation token to observe while waiting for the task to complete.</param>
|
|
|
|
|
/// <returns>The result of the task.</returns>
|
|
|
|
|
/// <exception cref="OperationCanceledException">The <paramref name="cancellationToken"/> was cancelled before the <paramref name="task"/> completed, or the <paramref name="task"/> raised an <see cref="OperationCanceledException"/>.</exception>
|
|
|
|
|
public static TResult WaitAndUnwrapException<TResult>(this Task<TResult> task, CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
task.Wait(cancellationToken);
|
|
|
|
|
return task.Result;
|
|
|
|
|
}
|
|
|
|
|
catch (AggregateException ex)
|
|
|
|
|
{
|
|
|
|
|
throw ExceptionHelpers.PrepareForRethrow(ex.InnerException);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Waits for the task to complete, but does not raise task exceptions. The task exception (if any) is unobserved.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="task">The task. May not be <c>null</c>.</param>
|
|
|
|
|
public static void WaitWithoutException(this Task task)
|
|
|
|
|
{
|
|
|
|
|
// Check to see if it's completed first, so we don't cause unnecessary allocation of a WaitHandle.
|
|
|
|
|
if (task.IsCompleted)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var asyncResult = (IAsyncResult)task;
|
|
|
|
|
asyncResult.AsyncWaitHandle.WaitOne();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Waits for the task to complete, but does not raise task exceptions. The task exception (if any) is unobserved.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="task">The task. May not be <c>null</c>.</param>
|
|
|
|
|
/// <param name="cancellationToken">A cancellation token to observe while waiting for the task to complete.</param>
|
|
|
|
|
/// <exception cref="OperationCanceledException">The <paramref name="cancellationToken"/> was cancelled before the <paramref name="task"/> completed.</exception>
|
|
|
|
|
public static void WaitWithoutException(this Task task, CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
// Check to see if it's completed first, so we don't cause unnecessary allocation of a WaitHandle.
|
|
|
|
|
if (task.IsCompleted)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
|
|
|
|
|
|
var index = WaitHandle.WaitAny(new[] { ((IAsyncResult)task).AsyncWaitHandle, cancellationToken.WaitHandle });
|
|
|
|
|
if (index != 0)
|
|
|
|
|
{
|
|
|
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|