Async functions and iterators are both rewritten by the compiler into state machines, so that a method can stop part-way and resume later (C# specification clauses 15.14 and 15.15).
async and await
An async method (C# 5) can contain await expressions. When
it awaits something not yet complete, the method returns to its caller, and resumes
later when the awaited operation finishes.
public async Task<int> CountLinesAsync(string path, CancellationToken token = default)
{
string text = await File.ReadAllTextAsync(path, token);
return text.Split('\n').Length;
}
int lines = await CountLinesAsync("a.txt");
| Return type | Meaning |
|---|---|
Task | No result; can be awaited |
Task<T> | A result of type T |
ValueTask, ValueTask<T> | Struct forms that avoid allocating when the result is already known (C# 7.0) |
| Other task-like types | Types named with [AsyncMethodBuilder] (clause 15.14.2) |
IAsyncEnumerable<T>, IAsyncEnumerator<T> | Async iterators (C# 8) |
void | Cannot be awaited; exceptions go to the synchronisation context. Meant for event handlers |
- An async method body runs synchronously until its first
awaitof an operation that is not already complete. - An async method cannot have
in,ref,ref readonlyoroutparameters. - The
asynckeyword is an implementation detail: it is not part of the signature, and an interface member cannot be declaredasync. awaitis a contextual keyword, and is a keyword only inside anasyncfunction (clause 12.9.9). Since C# 7.1Mainmay be async. Since C# 6awaitis allowed incatchandfinally.- Since C# 13, an async method may declare
reflocals andref structvariables, in code blocks without anawait.
The awaitable pattern
await e compiles against a pattern, not a fixed type (clause 12.9.9).
e needs a GetAwaiter() method, which may be an extension
method. The awaiter must have an IsCompleted property, a
GetResult() method, and implement
INotifyCompletion.
Synchronisation context
By default, the code after an await resumes on the captured
synchronisation context, such as a UI thread, if there is one.
task.ConfigureAwait(false) asks it not to, and is common in library
code. Blocking on a task with .Result or .Wait() can
deadlock in such a context.
Exceptions in async methods
An exception in an async method is stored in the returned task, and rethrown at the
await. .Wait() and .Result wrap it in an
AggregateException; await throws the original exception. In an
async void method an unhandled exception goes to the synchronisation context,
and usually ends the process.
Iterators
An iterator (C# 2, clause 15.15) is a method whose body uses yield, and
whose return type is IEnumerable, IEnumerator or their
generic forms. The compiler creates a class that produces the values on demand.
public static IEnumerable<int> Range(int from, int count)
{
for (int i = 0; i < count; i++)
{
yield return from + i;
}
yield break; // ends the sequence; rarely needed at the end of the method
}
foreach (int n in Range(1, 3)) Console.WriteLine(n);
- Execution is lazy. Nothing runs until the first
MoveNext, andforeachpulls one value at a time. - An iterator cannot have
in,reforoutparameters (clause 15.15.1). yield returncannot be in atryblock with acatch, nor in acatchorfinally. It can be in atrywith only afinally.yield breakcan be in atry, but not in afinally.- If the caller stops early, disposing the enumerator runs any pending
finallyblocks.foreachdoes this for you. - Since C# 13, an iterator may use
reflocals,ref structvariables andunsafecontexts, in code segments without ayield return.
Async streams
An async stream (C# 8) combines the two: an async method that returns
IAsyncEnumerable<T> and uses yield return. It is read with
await foreach.
public async IAsyncEnumerable<string> ReadLinesAsync(
string path,
[EnumeratorCancellation] CancellationToken token = default)
{
using var reader = File.OpenText(path);
while (await reader.ReadLineAsync(token) is { } line)
{
yield return line;
}
}
await foreach (var line in ReadLinesAsync("a.txt").WithCancellation(cts.Token))
{
Console.WriteLine(line);
}
[EnumeratorCancellation] marks the parameter that receives the token passed
to WithCancellation (clause 23.5.10). ConfigureAwait can also
be applied to the sequence. await using (see
Statements) disposes
IAsyncDisposable objects.