Skip to content

[Analyzer Proposal]: Replace 'suppress exceptions for single await' pattern with ConfigureAwaitOptions.SuppressThrowing analyzer/fix #120177

@DoctorKrolic

Description

@DoctorKrolic

Background and motivation

ConfigureAwaitOptions were added in .NET 8 and the original motivation was to allow await to avoid throwing an exception. This is a niche performance feature and I don't see it got much adoption in codebases outside .NET ecosystem itself. Event I myself am not used to this no-throwing pattern enough, so I may get a spark of realization a few days after the original work is done. Hence I believe there can be an analyzer suggesting to replace most common suppress exceptions patterns with this new option. Not only does this improve perf but also reduce nesting since try is a one nesting level + catch may add another one

API Proposal

Add an analyzer + code fix to suggest replacing suppress exceptions pattern for single await operation with ConfigureAwaitOptions.SuppressThrowing. Concrete example in the section below.

API Usage

  • Basic suppress throw:
// Before
try
{
    await someExpressionReturinngTask; // or `await someExpressionReturinngTask.ConfigureAwait(true)`
}
catch // or `catch (Exception)` or `catch (Exception ex)`
{
}

// After
await someExpressionReturinngTask.ConfigureAwait(ConfigureAwaitOptions.ContinueOnCapturedContext | ConfigureAwaitOptions.SuppressThrowing);
  • With ConfigureAwait(false):
// Before
try
{
    await someExpressionReturinngTask.ConfigureAwait(false);
}
catch
{
}

// After
await someExpressionReturinngTask.ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing);
  • When expression returns Task<T>, but the result is ignored:
// Before
try
{
    await someExpressionReturinngTaskOfT; // or `_ = await someExpressionReturinngTaskOfT`
}
catch
{
}

// After
await ((Task)someExpressionReturinngTaskOfT).ConfigureAwait(ConfigureAwaitOptions.ContinueOnCapturedContext | ConfigureAwaitOptions.SuppressThrowing);
  • When code inside catch does not depend on caught exception object:
// Before
bool hasErrors = false;
try
{
    await someExpressionReturinngTask;
}
catch
{
    hasErrors = true;
}

// After
var someExpressionTask = someExpressionReturinngTask;
await someExpressionTask.ConfigureAwait(ConfigureAwaitOptions.ContinueOnCapturedContext | ConfigureAwaitOptions.SuppressThrowing);
bool hasErrors = !someExpressionTask.IsCompletedSuccessfully;
// Before
try
{
    await someExpressionReturinngTask;
}
catch
{
    Console.WriteLine("This code does not depend on exception object");
}

// After
var someExpressionTask = someExpressionReturinngTask;
await someExpressionTask.ConfigureAwait(ConfigureAwaitOptions.ContinueOnCapturedContext | ConfigureAwaitOptions.SuppressThrowing);
if (!someExpressionTask.IsCompletedSuccessfully)
{
    Console.WriteLine("This code does not depend on exception object");
}

And any combinatorial combination of these cases.

Alternative Designs

No response

Risks

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    api-suggestionEarly API idea and discussion, it is NOT ready for implementationarea-System.Threading.Taskscode-analyzerMarks an issue that suggests a Roslyn analyzercode-fixerMarks an issue that suggests a Roslyn code fixeruntriagedNew issue has not been triaged by the area owner

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions