Skip to content

Commit

Permalink
Add AwaitExtensions.ConfigureAwaitForAggregateException extension m…
Browse files Browse the repository at this point in the history
…ethod

This allows awaiting a `Task` without losing all but the first of the `InnerExceptions` when it faults.

Besides adding tests for it, I modify an existing test to *use* it since it was failing with a timeout on occasion and I suspect the reason is found in another of the `Task.WhenAll` arguments' failures.
  • Loading branch information
AArnott committed Sep 15, 2020
1 parent 50c7a74 commit 0326567
Show file tree
Hide file tree
Showing 6 changed files with 224 additions and 35 deletions.
131 changes: 97 additions & 34 deletions src/Microsoft.VisualStudio.Threading/AwaitExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

#pragma warning disable CA1034 // Nested types should not be visible

namespace Microsoft.VisualStudio.Threading
{
using System;
Expand All @@ -9,7 +11,7 @@ namespace Microsoft.VisualStudio.Threading
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Security;
using System.Runtime.ExceptionServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Win32;
Expand Down Expand Up @@ -119,7 +121,6 @@ public static TaskAwaiter GetAwaiter(this WaitHandle handle)
/// <param name="yieldAwaitable">The result of <see cref="Task.Yield()"/>.</param>
/// <param name="continueOnCapturedContext">A value indicating whether the continuation should run on the captured <see cref="SynchronizationContext"/>, if any.</param>
/// <returns>An awaitable.</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "yieldAwaitable", Justification = "This allows the extension method syntax to work.")]
public static ConfiguredTaskYieldAwaitable ConfigureAwait(this YieldAwaitable yieldAwaitable, bool continueOnCapturedContext)
{
return new ConfiguredTaskYieldAwaitable(continueOnCapturedContext);
Expand Down Expand Up @@ -160,6 +161,19 @@ public static ExecuteContinuationSynchronouslyAwaitable<T> ConfigureAwaitRunInli
return new ExecuteContinuationSynchronouslyAwaitable<T>(antecedent);
}

/// <summary>
/// Returns an awaitable that will throw <see cref="AggregateException"/> from the <see cref="Task.Exception"/> property of the task if it faults.
/// </summary>
/// <param name="task">The task to track for completion.</param>
/// <param name="continueOnCapturedContext"><inheritdoc cref="Task.ConfigureAwait(bool)" path="/param[@name='continueOnCapturedContext']"/></param>
/// <returns>An awaitable that may throw <see cref="AggregateException"/>.</returns>
/// <remarks>
/// Awaiting a <see cref="Task"/> with its default <see cref="TaskAwaiter"/> only throws the first exception within <see cref="AggregateException.InnerExceptions"/>.
/// When you do not want to lose the detail of other inner exceptions, use this extension method.
/// </remarks>
/// <exception cref="AggregateException">Thrown when <paramref name="task"/> faults.</exception>
public static AggregateExceptionAwaitable ConfigureAwaitForAggregateException(this Task task, bool continueOnCapturedContext = true) => new AggregateExceptionAwaitable(task, continueOnCapturedContext);

/// <summary>
/// Returns a Task that completes when the specified registry key changes.
/// </summary>
Expand Down Expand Up @@ -218,13 +232,81 @@ private static async Task WaitForRegistryChangeAsync(SafeRegistryHandle registry
}
}

/// <summary>
/// The result of <see cref="ConfigureAwaitForAggregateException(Task, bool)"/> to prepare a <see cref="Task"/> to be awaited while throwing with all inner exceptions.
/// </summary>
[SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification = "Awaitables are not compared.")]
public readonly struct AggregateExceptionAwaitable
{
private readonly Task task;
private readonly bool continueOnCapturedContext;

/// <summary>
/// Initializes a new instance of the <see cref="AggregateExceptionAwaitable"/> struct.
/// </summary>
public AggregateExceptionAwaitable(Task task, bool continueOnCapturedContext)
{
this.task = task;
this.continueOnCapturedContext = continueOnCapturedContext;
}

/// <summary>
/// Gets an awaitable that schedules continuations on the specified scheduler.
/// </summary>
public AggregateExceptionAwaiter GetAwaiter()
{
return new AggregateExceptionAwaiter(this.task, this.continueOnCapturedContext);
}
}

/// <summary>
/// The result of <see cref="AggregateExceptionAwaitable.GetAwaiter"/> to prepare a <see cref="Task"/> to be awaited while throwing with all inner exceptions.
/// </summary>
[SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification = "Awaitables are not compared.")]
public readonly struct AggregateExceptionAwaiter : ICriticalNotifyCompletion
{
private readonly Task task;
private readonly bool continueOnCapturedContext;

/// <summary>
/// Initializes a new instance of the <see cref="AggregateExceptionAwaiter"/> struct.
/// </summary>
public AggregateExceptionAwaiter(Task task, bool continueOnCapturedContext)
{
this.task = task;
this.continueOnCapturedContext = continueOnCapturedContext;
}

/// <inheritdoc cref="TaskAwaiter.IsCompleted"/>
public bool IsCompleted => this.Awaiter.IsCompleted;

private ConfiguredTaskAwaitable.ConfiguredTaskAwaiter Awaiter => this.task.ConfigureAwait(this.continueOnCapturedContext).GetAwaiter();

/// <inheritdoc cref="TaskAwaiter.OnCompleted(Action)"/>
public void OnCompleted(Action continuation) => this.Awaiter.OnCompleted(continuation);

/// <inheritdoc cref="TaskAwaiter.UnsafeOnCompleted(Action)"/>
public void UnsafeOnCompleted(Action continuation) => this.Awaiter.UnsafeOnCompleted(continuation);

/// <inheritdoc cref="TaskAwaiter.GetResult" path="/summary"/>
/// <exception cref="OperationCanceledException">Thrown if the task was canceled.</exception>
/// <exception cref="AggregateException">Thrown if the task faulted.</exception>
public void GetResult()
{
if (this.task.Status == TaskStatus.Faulted && this.task.Exception is object)
{
ExceptionDispatchInfo.Capture(this.task.Exception).Throw();
}

this.Awaiter.GetResult();
}
}

/// <summary>
/// An awaitable that executes continuations on the specified task scheduler.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes")]
#pragma warning disable CA1034 // Nested types should not be visible
[SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification = "Awaitables are not compared.")]
public readonly struct TaskSchedulerAwaitable
#pragma warning restore CA1034 // Nested types should not be visible
{
/// <summary>
/// The scheduler for continuations.
Expand Down Expand Up @@ -253,7 +335,6 @@ public TaskSchedulerAwaitable(TaskScheduler taskScheduler, bool alwaysYield = fa
/// <summary>
/// Gets an awaitable that schedules continuations on the specified scheduler.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
public TaskSchedulerAwaiter GetAwaiter()
{
return new TaskSchedulerAwaiter(this.taskScheduler, this.alwaysYield);
Expand All @@ -263,10 +344,8 @@ public TaskSchedulerAwaiter GetAwaiter()
/// <summary>
/// An awaiter returned from <see cref="GetAwaiter(TaskScheduler)"/>.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes")]
#pragma warning disable CA1034 // Nested types should not be visible
[SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification = "Awaitables are not compared.")]
public readonly struct TaskSchedulerAwaiter : ICriticalNotifyCompletion
#pragma warning restore CA1034 // Nested types should not be visible
{
/// <summary>
/// The scheduler for continuations.
Expand Down Expand Up @@ -352,7 +431,7 @@ public void UnsafeOnCompleted(Action continuation)
/// <summary>
/// Does nothing.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
public void GetResult()
{
}
Expand All @@ -362,10 +441,8 @@ public void GetResult()
/// An awaitable that will always lead the calling async method to yield,
/// then immediately resume, possibly on the original <see cref="SynchronizationContext"/>.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes")]
#pragma warning disable CA1034 // Nested types should not be visible
[SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification = "Awaitables are not compared.")]
public readonly struct ConfiguredTaskYieldAwaitable
#pragma warning restore CA1034 // Nested types should not be visible
{
/// <summary>
/// A value indicating whether the continuation should run on the captured <see cref="SynchronizationContext"/>, if any.
Expand All @@ -385,18 +462,15 @@ public ConfiguredTaskYieldAwaitable(bool continueOnCapturedContext)
/// Gets the awaiter.
/// </summary>
/// <returns>The awaiter.</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
public ConfiguredTaskYieldAwaiter GetAwaiter() => new ConfiguredTaskYieldAwaiter(this.continueOnCapturedContext);
}

/// <summary>
/// An awaiter that will always lead the calling async method to yield,
/// then immediately resume, possibly on the original <see cref="SynchronizationContext"/>.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes")]
#pragma warning disable CA1034 // Nested types should not be visible
[SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification = "Awaitables are not compared.")]
public readonly struct ConfiguredTaskYieldAwaiter : ICriticalNotifyCompletion
#pragma warning restore CA1034 // Nested types should not be visible
{
/// <summary>
/// A value indicating whether the continuation should run on the captured <see cref="SynchronizationContext"/>, if any.
Expand All @@ -416,7 +490,7 @@ public ConfiguredTaskYieldAwaiter(bool continueOnCapturedContext)
/// Gets a value indicating whether the caller should yield.
/// </summary>
/// <value>Always false.</value>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
public bool IsCompleted => false;

/// <summary>
Expand Down Expand Up @@ -455,7 +529,7 @@ public void UnsafeOnCompleted(Action continuation)
/// <summary>
/// Does nothing.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
public void GetResult()
{
}
Expand All @@ -464,10 +538,8 @@ public void GetResult()
/// <summary>
/// A Task awaitable that has affinity to executing callbacks synchronously on the completing callstack.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes")]
#pragma warning disable CA1034 // Nested types should not be visible
[SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification = "Awaitables are not compared.")]
public readonly struct ExecuteContinuationSynchronouslyAwaitable
#pragma warning restore CA1034 // Nested types should not be visible
{
/// <summary>
/// The task whose completion will execute the continuation.
Expand All @@ -488,17 +560,14 @@ public ExecuteContinuationSynchronouslyAwaitable(Task antecedent)
/// Gets the awaiter.
/// </summary>
/// <returns>The awaiter.</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
public ExecuteContinuationSynchronouslyAwaiter GetAwaiter() => new ExecuteContinuationSynchronouslyAwaiter(this.antecedent);
}

/// <summary>
/// A Task awaiter that has affinity to executing callbacks synchronously on the completing callstack.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes")]
#pragma warning disable CA1034 // Nested types should not be visible
[SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification = "Awaitables are not compared.")]
public readonly struct ExecuteContinuationSynchronouslyAwaiter : INotifyCompletion
#pragma warning restore CA1034 // Nested types should not be visible
{
/// <summary>
/// The task whose completion will execute the continuation.
Expand Down Expand Up @@ -546,10 +615,8 @@ public void OnCompleted(Action continuation)
/// A Task awaitable that has affinity to executing callbacks synchronously on the completing callstack.
/// </summary>
/// <typeparam name="T">The type of value returned by the awaited <see cref="Task"/>.</typeparam>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes")]
#pragma warning disable CA1034 // Nested types should not be visible
[SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification = "Awaitables are not compared.")]
public readonly struct ExecuteContinuationSynchronouslyAwaitable<T>
#pragma warning restore CA1034 // Nested types should not be visible
{
/// <summary>
/// The task whose completion will execute the continuation.
Expand All @@ -570,18 +637,15 @@ public ExecuteContinuationSynchronouslyAwaitable(Task<T> antecedent)
/// Gets the awaiter.
/// </summary>
/// <returns>The awaiter.</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
public ExecuteContinuationSynchronouslyAwaiter<T> GetAwaiter() => new ExecuteContinuationSynchronouslyAwaiter<T>(this.antecedent);
}

/// <summary>
/// A Task awaiter that has affinity to executing callbacks synchronously on the completing callstack.
/// </summary>
/// <typeparam name="T">The type of value returned by the awaited <see cref="Task"/>.</typeparam>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes")]
#pragma warning disable CA1034 // Nested types should not be visible
[SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification = "Awaitables are not compared.")]
public readonly struct ExecuteContinuationSynchronouslyAwaiter<T> : INotifyCompletion
#pragma warning restore CA1034 // Nested types should not be visible
{
/// <summary>
/// The task whose completion will execute the continuation.
Expand All @@ -606,7 +670,6 @@ public ExecuteContinuationSynchronouslyAwaiter(Task<T> antecedent)
/// <summary>
/// Rethrows any exception thrown by the antecedent.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
public T GetResult() => this.antecedent.GetAwaiter().GetResult();

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,11 @@
#nullable enable
Microsoft.VisualStudio.Threading.AwaitExtensions.AggregateExceptionAwaitable
Microsoft.VisualStudio.Threading.AwaitExtensions.AggregateExceptionAwaitable.AggregateExceptionAwaitable(System.Threading.Tasks.Task! task, bool continueOnCapturedContext) -> void
Microsoft.VisualStudio.Threading.AwaitExtensions.AggregateExceptionAwaitable.GetAwaiter() -> Microsoft.VisualStudio.Threading.AwaitExtensions.AggregateExceptionAwaiter
Microsoft.VisualStudio.Threading.AwaitExtensions.AggregateExceptionAwaiter
Microsoft.VisualStudio.Threading.AwaitExtensions.AggregateExceptionAwaiter.AggregateExceptionAwaiter(System.Threading.Tasks.Task! task, bool continueOnCapturedContext) -> void
Microsoft.VisualStudio.Threading.AwaitExtensions.AggregateExceptionAwaiter.GetResult() -> void
Microsoft.VisualStudio.Threading.AwaitExtensions.AggregateExceptionAwaiter.IsCompleted.get -> bool
Microsoft.VisualStudio.Threading.AwaitExtensions.AggregateExceptionAwaiter.OnCompleted(System.Action! continuation) -> void
Microsoft.VisualStudio.Threading.AwaitExtensions.AggregateExceptionAwaiter.UnsafeOnCompleted(System.Action! continuation) -> void
static Microsoft.VisualStudio.Threading.AwaitExtensions.ConfigureAwaitForAggregateException(this System.Threading.Tasks.Task! task, bool continueOnCapturedContext = true) -> Microsoft.VisualStudio.Threading.AwaitExtensions.AggregateExceptionAwaitable
Original file line number Diff line number Diff line change
@@ -1 +1,11 @@
#nullable enable
Microsoft.VisualStudio.Threading.AwaitExtensions.AggregateExceptionAwaitable
Microsoft.VisualStudio.Threading.AwaitExtensions.AggregateExceptionAwaitable.AggregateExceptionAwaitable(System.Threading.Tasks.Task! task, bool continueOnCapturedContext) -> void
Microsoft.VisualStudio.Threading.AwaitExtensions.AggregateExceptionAwaitable.GetAwaiter() -> Microsoft.VisualStudio.Threading.AwaitExtensions.AggregateExceptionAwaiter
Microsoft.VisualStudio.Threading.AwaitExtensions.AggregateExceptionAwaiter
Microsoft.VisualStudio.Threading.AwaitExtensions.AggregateExceptionAwaiter.AggregateExceptionAwaiter(System.Threading.Tasks.Task! task, bool continueOnCapturedContext) -> void
Microsoft.VisualStudio.Threading.AwaitExtensions.AggregateExceptionAwaiter.GetResult() -> void
Microsoft.VisualStudio.Threading.AwaitExtensions.AggregateExceptionAwaiter.IsCompleted.get -> bool
Microsoft.VisualStudio.Threading.AwaitExtensions.AggregateExceptionAwaiter.OnCompleted(System.Action! continuation) -> void
Microsoft.VisualStudio.Threading.AwaitExtensions.AggregateExceptionAwaiter.UnsafeOnCompleted(System.Action! continuation) -> void
static Microsoft.VisualStudio.Threading.AwaitExtensions.ConfigureAwaitForAggregateException(this System.Threading.Tasks.Task! task, bool continueOnCapturedContext = true) -> Microsoft.VisualStudio.Threading.AwaitExtensions.AggregateExceptionAwaitable
Loading

0 comments on commit 0326567

Please sign in to comment.