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;
}

Remediation

Change the return type from void to Task (or Task<T> when the method produces a value). For event handlers whose framework-mandated signature forces void, wrap the body in a try/catch that handles every exception explicitly to prevent the process from terminating on an unobserved fault.