Async Task Not Void
ID |
csharp.async_task_not_void |
Severity |
high |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Reliability |
Language |
CSharp |
Tags |
async, reliability |
Description
Reports async void methods. Methods declared async void cannot be awaited and any
exception raised inside them propagates to the active SynchronizationContext,
typically crashing the process. Callers cannot observe completion or failure either.
Rationale
The contract of async is that the method returns control to the caller at the first
await, and the caller resumes when the returned Task completes. When the return
type is void, the caller has no Task to await: it cannot tell whether the work is
done, cannot retrieve a thrown exception, and cannot apply cancellation. The runtime
re-raises any unhandled exception on the captured synchronisation context, which in
most applications means an immediate process crash.
The only legitimate use case for async void is a top-level event handler whose
signature is fixed by the framework. Every other async method should return Task (or
Task<T>); fire-and-forget callers can opt-in explicitly with the discard pattern.
public class Worker
{
public async void Run() // FLAW — exceptions crash the process
{
await DoWorkAsync();
}
public async Task RunSafe() // OK — caller can await and observe
{
await DoWorkAsync();
}
public async Task<int> ComputeAsync() // OK — returns a value via Task<T>
{
return await Task.FromResult(42);
}
private Task DoWorkAsync() => Task.CompletedTask;
}