dotnet / dotnet/sdk

[Analyzer Proposal] Warn when an awaitable result is ignored outside an async method

Open
#56,294 4 comments 0 reactions 1 assignee Claimed by @tannergooding View on GitHub
Area-Microsoft.CodeAnalysis.NetAnalyzers Feature Request needs design
Dominant language
C#
Stars
3.2k
Forks
1.3k
PR merge metrics
PR metrics pending

Description

### Is your feature request related to a problem?

Calls that return an awaitable can be silently ignored when the containing method is not marked `async`. CS4014 only reports these calls inside `async` methods, so mistakes such as the following compile without a warning:

```csharp
[TestMethod]
public void AsyncThrow()
{
Assert.ThrowsAsync(async () =>
{
throw new ArgumentException("exception", "value");
});
}
```

The test completes before the assertion is observed and can incorrectly pass.

This is not specific to test frameworks. It also occurs when a synchronous method accidentally calls an async API after a refactoring, or when an async delegate is invoked without observing its returned task.

### Describe the solution you'd like

Add an opt-in `Microsoft.CodeAnalysis.NetAnalyzers` rule that reports an invocation used as an expression statement when its result is awaitable and completely ignored, regardless of whether the containing method is `async`.

The rule should not report when the result is observed in any way, including when it is:

- awaited;
- returned;
- assigned to a variable or member;
- passed to another method; or
- explicitly assigned to a discard, such as `_ = StartAsync();`, to document intentional fire-and-forget behavior.

A disabled-by-default rule would avoid introducing warnings into existing codebases while allowing projects that do not permit implicit fire-and-forget calls to enable it. The explicit-discard escape hatch also distinguishes accidental omission from intentional behavior, matching how developers commonly suppress CS4014.

The analysis should ideally support general awaitable types, not only `Task`, `Task`, `ValueTask`, and `ValueTask`.

### Alternatives you've considered

- Extending CS4014 was previously declined for compatibility reasons in dotnet/roslyn#20782, and the compiler team reiterated that decision in dotnet/roslyn#76721.
- Extending CA1806 unconditionally was proposed in dotnet/roslyn-analyzers#6181 and declined because intentional fire-and-forget patterns would create many false positives. Making the rule opt-in and accepting an explicit discard addresses that concern without changing existing defaults.
- VSTHRD110 from `Microsoft.VisualStudio.Threading.Analyzers` and MA0134 from `Meziantou.Analyzer` already implement similar behavior, but most .NET projects do not receive either analyzer by default.
- A framework-specific analyzer could catch this only in test methods, but the underlying bug pattern is general and would be better implemented once in the first-party .NET analyzers.

### Additional context

The motivating MSTest scenario is tracked in microsoft/testfx#9816. The expected corrected code is:

```csharp
[TestMethod]
public async Task AsyncThrow()
{
await Assert.ThrowsAsync(async () =>
{
throw new ArgumentException("exception", "value");
});
}
```

Existing implementations demonstrating the rule's usefulness:

- VSTHRD110: https://github.com/microsoft/vs-threading/blob/main/doc/analyzers/VSTHRD110.md
- MA0134: https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0134.md

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.