Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement NoThrowAwaitable for ValueTask and ValueTask<TResult> #1193

Merged
merged 2 commits into from
Jun 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
244 changes: 244 additions & 0 deletions src/Microsoft.VisualStudio.Threading/TplExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Security;
using System.Threading;
Expand Down Expand Up @@ -241,6 +242,51 @@ public static NoThrowTaskAwaitable NoThrowAwaitable(this Task task, bool capture
return new NoThrowTaskAwaitable(task, captureContext);
}

/// <summary>
/// Returns an awaitable for the specified task that will never throw, even if the source task
/// faults or is canceled.
/// </summary>
/// <param name="task">The task whose completion should signal the completion of the returned awaitable.</param>
/// <param name="captureContext">if set to <see langword="true"/> the continuation will be scheduled on the caller's context; <see langword="false"/> to always execute the continuation on the threadpool.</param>
/// <returns>An awaitable.</returns>
[SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "The receiver type is disjoint.")]
public static NoThrowValueTaskAwaitable NoThrowAwaitable(this ValueTask task, bool captureContext = true)
{
return new NoThrowValueTaskAwaitable(task, captureContext);
}

/// <summary>
/// Returns an awaitable for the specified task that will never throw, even if the source task
/// faults or is canceled.
/// </summary>
/// <remarks>
/// The awaitable returned by this method does not provide access to the result of a successfully-completed
/// <see cref="ValueTask{TResult}"/>. To await without throwing and use the resulting value, the following
/// pattern may be used:
///
/// <code>
/// var methodValueTask = MethodAsync().Preserve();
/// await methodValueTask.NoThrowAwaitable(true);
/// if (methodValueTask.IsCompletedSuccessfully)
/// {
/// var result = methodValueTask.Result;
/// }
/// else
/// {
/// var exception = methodValueTask.AsTask().Exception.InnerException;
/// }
/// </code>
/// </remarks>
/// <param name="task">The task whose completion should signal the completion of the returned awaitable.</param>
/// <param name="captureContext">if set to <see langword="true"/> the continuation will be scheduled on the caller's context; <see langword="false"/> to always execute the continuation on the threadpool.</param>
/// <returns>An awaitable.</returns>
/// <typeparam name="TResult">The type of the result.</typeparam>
[SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "The receiver type is disjoint.")]
public static NoThrowValueTaskAwaitable<TResult> NoThrowAwaitable<TResult>(this ValueTask<TResult> task, bool captureContext = true)
{
return new NoThrowValueTaskAwaitable<TResult>(task, captureContext);
}

/// <summary>
/// Consumes a task and doesn't do anything with it. Useful for fire-and-forget calls to async methods within async methods.
/// </summary>
Expand Down Expand Up @@ -764,6 +810,204 @@ public void GetResult()
}
}

/// <summary>
/// An awaitable that wraps a task and never throws an exception when waited on.
/// </summary>
public readonly struct NoThrowValueTaskAwaitable
{
/// <summary>
/// The task.
/// </summary>
private readonly ValueTask task;

/// <summary>
/// A value indicating whether the continuation should be scheduled on the current sync context.
/// </summary>
private readonly bool captureContext;

/// <summary>
/// Initializes a new instance of the <see cref="NoThrowValueTaskAwaitable"/> struct.
/// </summary>
/// <param name="task">The task.</param>
/// <param name="captureContext">Whether the continuation should be scheduled on the current sync context.</param>
public NoThrowValueTaskAwaitable(ValueTask task, bool captureContext)
{
this.task = task.Preserve();
this.captureContext = captureContext;
}

/// <summary>
/// Gets the awaiter.
/// </summary>
/// <returns>The awaiter.</returns>
public NoThrowValueTaskAwaiter GetAwaiter()
{
return new NoThrowValueTaskAwaiter(this.task, this.captureContext);
}
}

/// <summary>
/// An awaiter that wraps a task and never throws an exception when waited on.
/// </summary>
public readonly struct NoThrowValueTaskAwaiter : ICriticalNotifyCompletion
{
/// <summary>
/// The task.
/// </summary>
private readonly ValueTask task;

/// <summary>
/// A value indicating whether the continuation should be scheduled on the current sync context.
/// </summary>
private readonly bool captureContext;

/// <summary>
/// Initializes a new instance of the <see cref="NoThrowValueTaskAwaiter"/> struct.
/// </summary>
/// <param name="task">The task.</param>
/// <param name="captureContext">if set to <see langword="true"/> [capture context].</param>
public NoThrowValueTaskAwaiter(ValueTask task, bool captureContext)
{
this.task = task;
this.captureContext = captureContext;
}

/// <summary>
/// Gets a value indicating whether the task has completed.
/// </summary>
public bool IsCompleted
{
get { return this.task.IsCompleted; }
}

/// <summary>
/// Schedules a delegate for execution at the conclusion of a task's execution.
/// </summary>
/// <param name="continuation">The action.</param>
public void OnCompleted(Action continuation)
{
this.task.ConfigureAwait(this.captureContext).GetAwaiter().OnCompleted(continuation);
}

/// <summary>
/// Schedules a delegate for execution at the conclusion of a task's execution
/// without capturing the ExecutionContext.
/// </summary>
/// <param name="continuation">The action.</param>
public void UnsafeOnCompleted(Action continuation)
{
this.task.ConfigureAwait(this.captureContext).GetAwaiter().UnsafeOnCompleted(continuation);
}

/// <summary>
/// Does nothing.
/// </summary>
public void GetResult()
{
// No need to do anything with 'task' because we already called Preserve on it.
}
}

/// <summary>
/// An awaitable that wraps a <see cref="ValueTask{TResult}"/> and never throws an exception when waited on.
/// </summary>
/// <typeparam name="TResult">The type of the result.</typeparam>
public readonly struct NoThrowValueTaskAwaitable<TResult>
{
/// <summary>
/// The task.
/// </summary>
private readonly ValueTask<TResult> task;

/// <summary>
/// A value indicating whether the continuation should be scheduled on the current sync context.
/// </summary>
private readonly bool captureContext;

/// <summary>
/// Initializes a new instance of the <see cref="NoThrowValueTaskAwaitable{TResult}" /> struct.
/// </summary>
/// <param name="task">The task.</param>
/// <param name="captureContext">Whether the continuation should be scheduled on the current sync context.</param>
public NoThrowValueTaskAwaitable(ValueTask<TResult> task, bool captureContext)
{
this.task = task.Preserve();
this.captureContext = captureContext;
}

/// <summary>
/// Gets the awaiter.
/// </summary>
/// <returns>The awaiter.</returns>
public NoThrowValueTaskAwaiter<TResult> GetAwaiter()
{
return new NoThrowValueTaskAwaiter<TResult>(this.task, this.captureContext);
}
}

/// <summary>
/// An awaiter that wraps a task and never throws an exception when waited on.
/// </summary>
/// <typeparam name="TResult">The type of the result.</typeparam>
public readonly struct NoThrowValueTaskAwaiter<TResult> : ICriticalNotifyCompletion
{
/// <summary>
/// The task.
/// </summary>
private readonly ValueTask<TResult> task;

/// <summary>
/// A value indicating whether the continuation should be scheduled on the current sync context.
/// </summary>
private readonly bool captureContext;

/// <summary>
/// Initializes a new instance of the <see cref="NoThrowValueTaskAwaiter{TResult}"/> struct.
/// </summary>
/// <param name="task">The task.</param>
/// <param name="captureContext">if set to <see langword="true"/> [capture context].</param>
public NoThrowValueTaskAwaiter(ValueTask<TResult> task, bool captureContext)
{
this.task = task;
this.captureContext = captureContext;
}

/// <summary>
/// Gets a value indicating whether the task has completed.
/// </summary>
public bool IsCompleted
{
get { return this.task.IsCompleted; }
}

/// <summary>
/// Schedules a delegate for execution at the conclusion of a task's execution.
/// </summary>
/// <param name="continuation">The action.</param>
public void OnCompleted(Action continuation)
{
this.task.ConfigureAwait(this.captureContext).GetAwaiter().OnCompleted(continuation);
}

/// <summary>
/// Schedules a delegate for execution at the conclusion of a task's execution
/// without capturing the ExecutionContext.
/// </summary>
/// <param name="continuation">The action.</param>
public void UnsafeOnCompleted(Action continuation)
{
this.task.ConfigureAwait(this.captureContext).GetAwaiter().UnsafeOnCompleted(continuation);
}

/// <summary>
/// Does nothing.
/// </summary>
public void GetResult()
{
// No need to do anything with 'task' because we already called Preserve on it.
}
}

/// <summary>
/// A state bag for the <see cref="FollowCancelableTaskToCompletion"/> method.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,24 @@
Microsoft.VisualStudio.Threading.JoinableTaskFactory.MainThreadAwaitable.NoThrowAwaitable() -> Microsoft.VisualStudio.Threading.JoinableTaskFactory.MainThreadAwaitable
Microsoft.VisualStudio.Threading.TplExtensions.NoThrowValueTaskAwaitable
Microsoft.VisualStudio.Threading.TplExtensions.NoThrowValueTaskAwaitable.GetAwaiter() -> Microsoft.VisualStudio.Threading.TplExtensions.NoThrowValueTaskAwaiter
Microsoft.VisualStudio.Threading.TplExtensions.NoThrowValueTaskAwaitable.NoThrowValueTaskAwaitable(System.Threading.Tasks.ValueTask task, bool captureContext) -> void
Microsoft.VisualStudio.Threading.TplExtensions.NoThrowValueTaskAwaitable<TResult>
Microsoft.VisualStudio.Threading.TplExtensions.NoThrowValueTaskAwaitable<TResult>.GetAwaiter() -> Microsoft.VisualStudio.Threading.TplExtensions.NoThrowValueTaskAwaiter<TResult>
Microsoft.VisualStudio.Threading.TplExtensions.NoThrowValueTaskAwaitable<TResult>.NoThrowValueTaskAwaitable(System.Threading.Tasks.ValueTask<TResult> task, bool captureContext) -> void
Microsoft.VisualStudio.Threading.TplExtensions.NoThrowValueTaskAwaiter
Microsoft.VisualStudio.Threading.TplExtensions.NoThrowValueTaskAwaiter.GetResult() -> void
Microsoft.VisualStudio.Threading.TplExtensions.NoThrowValueTaskAwaiter.IsCompleted.get -> bool
Microsoft.VisualStudio.Threading.TplExtensions.NoThrowValueTaskAwaiter.NoThrowValueTaskAwaiter(System.Threading.Tasks.ValueTask task, bool captureContext) -> void
Microsoft.VisualStudio.Threading.TplExtensions.NoThrowValueTaskAwaiter.OnCompleted(System.Action! continuation) -> void
Microsoft.VisualStudio.Threading.TplExtensions.NoThrowValueTaskAwaiter.UnsafeOnCompleted(System.Action! continuation) -> void
Microsoft.VisualStudio.Threading.TplExtensions.NoThrowValueTaskAwaiter<TResult>
Microsoft.VisualStudio.Threading.TplExtensions.NoThrowValueTaskAwaiter<TResult>.GetResult() -> void
Microsoft.VisualStudio.Threading.TplExtensions.NoThrowValueTaskAwaiter<TResult>.IsCompleted.get -> bool
Microsoft.VisualStudio.Threading.TplExtensions.NoThrowValueTaskAwaiter<TResult>.NoThrowValueTaskAwaiter(System.Threading.Tasks.ValueTask<TResult> task, bool captureContext) -> void
Microsoft.VisualStudio.Threading.TplExtensions.NoThrowValueTaskAwaiter<TResult>.OnCompleted(System.Action! continuation) -> void
Microsoft.VisualStudio.Threading.TplExtensions.NoThrowValueTaskAwaiter<TResult>.UnsafeOnCompleted(System.Action! continuation) -> void
static Microsoft.VisualStudio.Threading.TplExtensions.NoThrowAwaitable(this System.Threading.Tasks.ValueTask task, bool captureContext = true) -> Microsoft.VisualStudio.Threading.TplExtensions.NoThrowValueTaskAwaitable
static Microsoft.VisualStudio.Threading.TplExtensions.NoThrowAwaitable<TResult>(this System.Threading.Tasks.ValueTask<TResult> task, bool captureContext = true) -> Microsoft.VisualStudio.Threading.TplExtensions.NoThrowValueTaskAwaitable<TResult>
virtual Microsoft.VisualStudio.Threading.AsyncReaderWriterResourceLock<TMoniker, TResource>.GetTaskSchedulerToPrepareResourcesForConcurrentAccess(TResource! resource) -> System.Threading.Tasks.TaskScheduler!
Microsoft.VisualStudio.Threading.JoinableTaskContext.Capture() -> string?
Microsoft.VisualStudio.Threading.JoinableTaskFactory.RunAsync(System.Func<System.Threading.Tasks.Task!>! asyncMethod, string? parentToken, Microsoft.VisualStudio.Threading.JoinableTaskCreationOptions creationOptions) -> Microsoft.VisualStudio.Threading.JoinableTask!
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,24 @@
Microsoft.VisualStudio.Threading.JoinableTaskFactory.MainThreadAwaitable.NoThrowAwaitable() -> Microsoft.VisualStudio.Threading.JoinableTaskFactory.MainThreadAwaitable
Microsoft.VisualStudio.Threading.TplExtensions.NoThrowValueTaskAwaitable
Microsoft.VisualStudio.Threading.TplExtensions.NoThrowValueTaskAwaitable.GetAwaiter() -> Microsoft.VisualStudio.Threading.TplExtensions.NoThrowValueTaskAwaiter
Microsoft.VisualStudio.Threading.TplExtensions.NoThrowValueTaskAwaitable.NoThrowValueTaskAwaitable(System.Threading.Tasks.ValueTask task, bool captureContext) -> void
Microsoft.VisualStudio.Threading.TplExtensions.NoThrowValueTaskAwaitable<TResult>
Microsoft.VisualStudio.Threading.TplExtensions.NoThrowValueTaskAwaitable<TResult>.GetAwaiter() -> Microsoft.VisualStudio.Threading.TplExtensions.NoThrowValueTaskAwaiter<TResult>
Microsoft.VisualStudio.Threading.TplExtensions.NoThrowValueTaskAwaitable<TResult>.NoThrowValueTaskAwaitable(System.Threading.Tasks.ValueTask<TResult> task, bool captureContext) -> void
Microsoft.VisualStudio.Threading.TplExtensions.NoThrowValueTaskAwaiter
Microsoft.VisualStudio.Threading.TplExtensions.NoThrowValueTaskAwaiter.GetResult() -> void
Microsoft.VisualStudio.Threading.TplExtensions.NoThrowValueTaskAwaiter.IsCompleted.get -> bool
Microsoft.VisualStudio.Threading.TplExtensions.NoThrowValueTaskAwaiter.NoThrowValueTaskAwaiter(System.Threading.Tasks.ValueTask task, bool captureContext) -> void
Microsoft.VisualStudio.Threading.TplExtensions.NoThrowValueTaskAwaiter.OnCompleted(System.Action! continuation) -> void
Microsoft.VisualStudio.Threading.TplExtensions.NoThrowValueTaskAwaiter.UnsafeOnCompleted(System.Action! continuation) -> void
Microsoft.VisualStudio.Threading.TplExtensions.NoThrowValueTaskAwaiter<TResult>
Microsoft.VisualStudio.Threading.TplExtensions.NoThrowValueTaskAwaiter<TResult>.GetResult() -> void
Microsoft.VisualStudio.Threading.TplExtensions.NoThrowValueTaskAwaiter<TResult>.IsCompleted.get -> bool
Microsoft.VisualStudio.Threading.TplExtensions.NoThrowValueTaskAwaiter<TResult>.NoThrowValueTaskAwaiter(System.Threading.Tasks.ValueTask<TResult> task, bool captureContext) -> void
Microsoft.VisualStudio.Threading.TplExtensions.NoThrowValueTaskAwaiter<TResult>.OnCompleted(System.Action! continuation) -> void
Microsoft.VisualStudio.Threading.TplExtensions.NoThrowValueTaskAwaiter<TResult>.UnsafeOnCompleted(System.Action! continuation) -> void
static Microsoft.VisualStudio.Threading.TplExtensions.NoThrowAwaitable(this System.Threading.Tasks.ValueTask task, bool captureContext = true) -> Microsoft.VisualStudio.Threading.TplExtensions.NoThrowValueTaskAwaitable
static Microsoft.VisualStudio.Threading.TplExtensions.NoThrowAwaitable<TResult>(this System.Threading.Tasks.ValueTask<TResult> task, bool captureContext = true) -> Microsoft.VisualStudio.Threading.TplExtensions.NoThrowValueTaskAwaitable<TResult>
virtual Microsoft.VisualStudio.Threading.AsyncReaderWriterResourceLock<TMoniker, TResource>.GetTaskSchedulerToPrepareResourcesForConcurrentAccess(TResource! resource) -> System.Threading.Tasks.TaskScheduler!
Microsoft.VisualStudio.Threading.JoinableTaskContext.Capture() -> string?
Microsoft.VisualStudio.Threading.JoinableTaskFactory.RunAsync(System.Func<System.Threading.Tasks.Task!>! asyncMethod, string? parentToken, Microsoft.VisualStudio.Threading.JoinableTaskCreationOptions creationOptions) -> Microsoft.VisualStudio.Threading.JoinableTask!
Expand Down
Loading