-
Notifications
You must be signed in to change notification settings - Fork 0
PA00001
| Category | Usage |
| Severity | Warning |
A method has a body (or expression body), returns Task/ValueTask, and isn't marked async. Interface and abstract signatures aren't flagged: there's no body to make async.
A method shaped like an async method but not marked async usually means one of two things:
- A: It's dead code waiting for an
awaitthat was never added - B: It's deliberately forwarding a task without awaiting it.
Without async/await, the compiler doesn't build a state machine for the method, so its frame doesn't participate in the async call chain. If an exception surfaces later in whatever the task represents, the stack trace jumps straight to wherever the task was eventually awaited.
Task Sync() => DoWorkAsync(); // PA00001
async Task Async() => await DoWorkAsync(); // fine
async Task DoWorkAsync()
{
await Task.Delay(10);
throw new InvalidOperationException("boom");
}Calling Sync(), the exception's stack trace (simplified) reads:
System.InvalidOperationException: boom
at DoWorkAsync()
at Program.<Main>d__0.MoveNext()
Calling Async(), it reads:
System.InvalidOperationException: boom
at DoWorkAsync()
at Async()
at Program.<Main>d__0.MoveNext()
Sync() clocked out before DoWorkAsync got to the interesting part, so it isn't around to answer for it. That method disappears from a debugger, from logs, from an error-reporting tool: from the whole story of how the failure happened. Root-causing production incidents gets harder for the sake of skipping one await.
Add the async modifier and await the work being done:
// Before - PA00001: "Greet returns a Task and should be async"
public Task Greet()
{
return Task.CompletedTask;
}// After
public async Task Greet()
{
await Task.CompletedTask;
}If a method genuinely just forwards an already-completed or cached task with no await-able work of its own, suppress the specific instance rather than disabling the rule:
#pragma warning disable PA00001 // Intentionally synchronous - no work to await
public Task Greet() => Task.CompletedTask;
#pragma warning restore PA00001A decorator chain is a common example. A pure passthrough delegating to inner can be noise in a stacktrace and is a good candidate for suppression:
public class LoggingRepository : IRepository
{
private readonly IRepository _inner;
public LoggingRepository(IRepository inner) => _inner = inner;
#pragma warning disable PA00001 // Pure delegation - nothing in this frame to root-cause
public Task<Order> GetOrderAsync(int id) => _inner.GetOrderAsync(id);
#pragma warning restore PA00001
}