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(); } } } }