Skip to content
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
Add ConfigureAwaitOptions polyfill for pre-net8.0
 Closes #527

 - Add ConfigureAwaitOptions [Flags] enum with None,
   ContinueOnCapturedContext, SuppressThrowing, and ForceYielding
 - Add Task.ConfigureAwait(ConfigureAwaitOptions) extension method
 - Add Task<TResult>.ConfigureAwait(ConfigureAwaitOptions) extension method
 - Task<TResult> rejects SuppressThrowing (matches BCL behavior)
 - Invalid flag combinations throw ArgumentOutOfRangeException
 - ForceYielding implemented via Task.Yield() wrapper
 - SuppressThrowing implemented via try/catch wrapper
 - Restructure Polyfill_Task.cs to use inner #if guards
 - Add tests, consumption usage, and Split files
  • Loading branch information
paulomorgado committed Apr 10, 2026
commit a852c6e075c3756e67d6cbd654d67d464dd32380
7 changes: 7 additions & 0 deletions src/Consume/Consume.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1400,6 +1400,13 @@ void Task_Methods()
new Task<int>(func).WaitAsync(CancellationToken.None);
new Task<int>(func).WaitAsync(TimeSpan.Zero);
new Task<int>(func).WaitAsync(TimeSpan.Zero, CancellationToken.None);
Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.None);
Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ContinueOnCapturedContext);
Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing);
Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding);
Task.FromResult(0).ConfigureAwait(ConfigureAwaitOptions.None);
Task.FromResult(0).ConfigureAwait(ConfigureAwaitOptions.ContinueOnCapturedContext);
Task.FromResult(0).ConfigureAwait(ConfigureAwaitOptions.ForceYielding);
}

#if FeatureMemory
Expand Down
44 changes: 44 additions & 0 deletions src/Polyfill/ConfigureAwaitOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#if !NET8_0_OR_GREATER

namespace System.Threading.Tasks;

/// <summary>
/// Options to control behavior when awaiting.
/// </summary>
//Link: https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.configureawaitoptions?view=net-11.0
[Flags]
#if PolyUseEmbeddedAttribute
[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
#endif
#if PolyPublic
public
#endif
enum ConfigureAwaitOptions
{
/// <summary>
/// No options specified.
/// </summary>
None = 0,

/// <summary>
/// Attempts to marshal the continuation back to the original <see cref="System.Threading.SynchronizationContext"/>
/// or <see cref="System.Threading.Tasks.TaskScheduler"/> present on the originating thread at the time of the await.
/// </summary>
ContinueOnCapturedContext = 1,

/// <summary>
/// Avoids throwing an exception at the completion of awaiting a <see cref="Task"/> that ends
/// in the <see cref="TaskStatus.Faulted"/> or <see cref="TaskStatus.Canceled"/> state.
/// </summary>
SuppressThrowing = 2,

/// <summary>
/// Forces an await on an already completed <see cref="Task"/> to behave as if the <see cref="Task"/>
/// wasn't yet completed, such that the current asynchronous method will be forced to yield its execution.
/// </summary>
ForceYielding = 4
}

#else
[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Threading.Tasks.ConfigureAwaitOptions))]
#endif
78 changes: 78 additions & 0 deletions src/Polyfill/Polyfill_Task.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,81 @@ public static async Task<TResult> WaitAsync<TResult>(
}

#endif
#if !NET8_0_OR_GREATER

/// <summary>Configures an awaiter used to await this <see cref="Task"/>.</summary>
//Link: https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.configureawait?view=net-11.0#system-threading-tasks-task-configureawait(system-threading-tasks-configureawaitoptions)
public static ConfiguredTaskAwaitable ConfigureAwait(this Task target, ConfigureAwaitOptions options)
{
if ((options & ~(ConfigureAwaitOptions.ContinueOnCapturedContext |
ConfigureAwaitOptions.SuppressThrowing |
ConfigureAwaitOptions.ForceYielding)) != 0)
{
throw new ArgumentOutOfRangeException(nameof(options));
}

var task = target;

if ((options & ConfigureAwaitOptions.ForceYielding) != 0)
{
task = ForceYieldAsync(task);

static async Task ForceYieldAsync(Task t)
{
await Task.Yield();
await t;
}
}

if ((options & ConfigureAwaitOptions.SuppressThrowing) != 0)
{
task = SuppressThrowAsync(task);

static async Task SuppressThrowAsync(Task t)
{
try
{
await t;
}
catch
{
}
}
}

return task.ConfigureAwait(
(options & ConfigureAwaitOptions.ContinueOnCapturedContext) != 0);
}

/// <summary>Configures an awaiter used to await this <see cref="Task{TResult}"/>.</summary>
//Link: https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task-1.configureawait?view=net-11.0#system-threading-tasks-task-1-configureawait(system-threading-tasks-configureawaitoptions)
public static ConfiguredTaskAwaitable<TResult> ConfigureAwait<TResult>(
this Task<TResult> target,
ConfigureAwaitOptions options)
{
if ((options & ~(ConfigureAwaitOptions.ContinueOnCapturedContext |
ConfigureAwaitOptions.ForceYielding)) != 0)
{
throw new ArgumentOutOfRangeException(nameof(options));
}

var task = target;

if ((options & ConfigureAwaitOptions.ForceYielding) != 0)
{
task = ForceYieldAsync(task);

static async Task<TResult> ForceYieldAsync(Task<TResult> t)
{
await Task.Yield();
return await t;
}
}

return task.ConfigureAwait(
(options & ConfigureAwaitOptions.ContinueOnCapturedContext) != 0);
}

#endif
}

35 changes: 35 additions & 0 deletions src/Split/net461/ConfigureAwaitOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// <auto-generated />
#pragma warning disable
namespace System.Threading.Tasks;
/// <summary>
/// Options to control behavior when awaiting.
/// </summary>
[System.Flags]
#if PolyUseEmbeddedAttribute
[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
#endif
#if PolyPublic
public
#endif
enum ConfigureAwaitOptions
{
/// <summary>
/// No options specified.
/// </summary>
None = 0,
/// <summary>
/// Attempts to marshal the continuation back to the original <see cref="System.Threading.SynchronizationContext"/> or
/// <see cref="System.Threading.Tasks.TaskScheduler"/> present on the originating thread at the time of the await.
/// </summary>
ContinueOnCapturedContext = 1,
/// <summary>
/// Avoids throwing an exception at the completion of awaiting a <see cref="Task"/> that ends in the
/// <see cref="TaskStatus.Faulted"/> or <see cref="TaskStatus.Canceled"/> state.
/// </summary>
SuppressThrowing = 2,
/// <summary>
/// Forces an await on an already completed <see cref="Task"/> to behave as if the <see cref="Task"/> wasn't yet
/// completed, such that the current asynchronous method will be forced to yield its execution.
/// </summary>
ForceYielding = 4
}
59 changes: 59 additions & 0 deletions src/Split/net461/Polyfill_Task.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,63 @@ public static async Task<TResult> WaitAsync<TResult>(
await ((Task) target).WaitAsync(timeout, cancellationToken);
return target.Result;
}
/// <summary>Configures an awaiter used to await this <see cref="Task"/>.</summary>
public static System.Runtime.CompilerServices.ConfiguredTaskAwaitable ConfigureAwait(this Task target, ConfigureAwaitOptions options)
{
if ((options & ~(ConfigureAwaitOptions.ContinueOnCapturedContext |
ConfigureAwaitOptions.SuppressThrowing |
ConfigureAwaitOptions.ForceYielding)) != 0)
{
throw new System.ArgumentOutOfRangeException(nameof(options));
}
var task = target;
if ((options & ConfigureAwaitOptions.ForceYielding) != 0)
{
task = ForceYieldAsync(task);
static async Task ForceYieldAsync(Task t)
{
await Task.Yield();
await t;
}
}
if ((options & ConfigureAwaitOptions.SuppressThrowing) != 0)
{
task = SuppressThrowAsync(task);
static async Task SuppressThrowAsync(Task t)
{
try
{
await t;
}
catch
{
}
}
}
return task.ConfigureAwait(
(options & ConfigureAwaitOptions.ContinueOnCapturedContext) != 0);
}
/// <summary>Configures an awaiter used to await this <see cref="Task{TResult}"/>.</summary>
public static System.Runtime.CompilerServices.ConfiguredTaskAwaitable<TResult> ConfigureAwait<TResult>(
this Task<TResult> target,
ConfigureAwaitOptions options)
{
if ((options & ~(ConfigureAwaitOptions.ContinueOnCapturedContext |
ConfigureAwaitOptions.ForceYielding)) != 0)
{
throw new System.ArgumentOutOfRangeException(nameof(options));
}
var task = target;
if ((options & ConfigureAwaitOptions.ForceYielding) != 0)
{
task = ForceYieldAsync(task);
static async Task<TResult> ForceYieldAsync(Task<TResult> t)
{
await Task.Yield();
return await t;
}
}
return task.ConfigureAwait(
(options & ConfigureAwaitOptions.ContinueOnCapturedContext) != 0);
}
}
35 changes: 35 additions & 0 deletions src/Split/net462/ConfigureAwaitOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// <auto-generated />
#pragma warning disable
namespace System.Threading.Tasks;
/// <summary>
/// Options to control behavior when awaiting.
/// </summary>
[System.Flags]
#if PolyUseEmbeddedAttribute
[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
#endif
#if PolyPublic
public
#endif
enum ConfigureAwaitOptions
{
/// <summary>
/// No options specified.
/// </summary>
None = 0,
/// <summary>
/// Attempts to marshal the continuation back to the original <see cref="System.Threading.SynchronizationContext"/> or
/// <see cref="System.Threading.Tasks.TaskScheduler"/> present on the originating thread at the time of the await.
/// </summary>
ContinueOnCapturedContext = 1,
/// <summary>
/// Avoids throwing an exception at the completion of awaiting a <see cref="Task"/> that ends in the
/// <see cref="TaskStatus.Faulted"/> or <see cref="TaskStatus.Canceled"/> state.
/// </summary>
SuppressThrowing = 2,
/// <summary>
/// Forces an await on an already completed <see cref="Task"/> to behave as if the <see cref="Task"/> wasn't yet
/// completed, such that the current asynchronous method will be forced to yield its execution.
/// </summary>
ForceYielding = 4
}
59 changes: 59 additions & 0 deletions src/Split/net462/Polyfill_Task.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,63 @@ public static async Task<TResult> WaitAsync<TResult>(
await ((Task) target).WaitAsync(timeout, cancellationToken);
return target.Result;
}
/// <summary>Configures an awaiter used to await this <see cref="Task"/>.</summary>
public static System.Runtime.CompilerServices.ConfiguredTaskAwaitable ConfigureAwait(this Task target, ConfigureAwaitOptions options)
{
if ((options & ~(ConfigureAwaitOptions.ContinueOnCapturedContext |
ConfigureAwaitOptions.SuppressThrowing |
ConfigureAwaitOptions.ForceYielding)) != 0)
{
throw new System.ArgumentOutOfRangeException(nameof(options));
}
var task = target;
if ((options & ConfigureAwaitOptions.ForceYielding) != 0)
{
task = ForceYieldAsync(task);
static async Task ForceYieldAsync(Task t)
{
await Task.Yield();
await t;
}
}
if ((options & ConfigureAwaitOptions.SuppressThrowing) != 0)
{
task = SuppressThrowAsync(task);
static async Task SuppressThrowAsync(Task t)
{
try
{
await t;
}
catch
{
}
}
}
return task.ConfigureAwait(
(options & ConfigureAwaitOptions.ContinueOnCapturedContext) != 0);
}
/// <summary>Configures an awaiter used to await this <see cref="Task{TResult}"/>.</summary>
public static System.Runtime.CompilerServices.ConfiguredTaskAwaitable<TResult> ConfigureAwait<TResult>(
this Task<TResult> target,
ConfigureAwaitOptions options)
{
if ((options & ~(ConfigureAwaitOptions.ContinueOnCapturedContext |
ConfigureAwaitOptions.ForceYielding)) != 0)
{
throw new System.ArgumentOutOfRangeException(nameof(options));
}
var task = target;
if ((options & ConfigureAwaitOptions.ForceYielding) != 0)
{
task = ForceYieldAsync(task);
static async Task<TResult> ForceYieldAsync(Task<TResult> t)
{
await Task.Yield();
return await t;
}
}
return task.ConfigureAwait(
(options & ConfigureAwaitOptions.ContinueOnCapturedContext) != 0);
}
}
35 changes: 35 additions & 0 deletions src/Split/net47/ConfigureAwaitOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// <auto-generated />
#pragma warning disable
namespace System.Threading.Tasks;
/// <summary>
/// Options to control behavior when awaiting.
/// </summary>
[System.Flags]
#if PolyUseEmbeddedAttribute
[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
#endif
#if PolyPublic
public
#endif
enum ConfigureAwaitOptions
{
/// <summary>
/// No options specified.
/// </summary>
None = 0,
/// <summary>
/// Attempts to marshal the continuation back to the original <see cref="System.Threading.SynchronizationContext"/> or
/// <see cref="System.Threading.Tasks.TaskScheduler"/> present on the originating thread at the time of the await.
/// </summary>
ContinueOnCapturedContext = 1,
/// <summary>
/// Avoids throwing an exception at the completion of awaiting a <see cref="Task"/> that ends in the
/// <see cref="TaskStatus.Faulted"/> or <see cref="TaskStatus.Canceled"/> state.
/// </summary>
SuppressThrowing = 2,
/// <summary>
/// Forces an await on an already completed <see cref="Task"/> to behave as if the <see cref="Task"/> wasn't yet
/// completed, such that the current asynchronous method will be forced to yield its execution.
/// </summary>
ForceYielding = 4
}
Loading
Loading