Chapter 5. Writing asynchronous code
覆盖Introducing async functions、Asynchrony thinking、Declarations、Await、Return wrapping、Flow、Async lambdas、Custom task types、Async Main与Usage tips。
学习目标
- 能绘制async invocation、await fast/slow path、return wrapping与task completion的完整timeline
- 能比较async lambda、async void、custom task-like与async Main的caller-visible completion和fault contract
- 能设计dependent/concurrent I/O、cancellation、sync boundary与ValueTask usage,并复现race和all-fault行为
机制总览
Chapter 5. Writing asynchronous code:机制路径
- 1
为什么Asynchrony不是“把方法放到后台线程”
Asynchrony允许operation在等待外部completion时不占用当前call stack/thread,并在完成后继续logical flow。
- 2
Introducing asynchronous func…
Async function让method/lambda包含await并返回task-like completion。Caller获得Task后可以await、compose、cancel source或observe fault。它比callback参数更清楚地把completion作为value…
- 3
Thinking about asynchrony
先区分I/O-bound与CPU-bound。I/O async依赖nonblocking underlying API,等待期间通常无需worker thread;CPU work要并行则需要scheduler/thread resources,不能仅加 async 。Latency、throug…
章级决策实验
Chapter 5. Writing asynchronous code:机制与证据
切换《Chapter 5. Writing asynchronous code》的三个关键教学阶段,先解释机制,再用运行与失败证据验证结论。
选择推理阶段
当前阶段 · 为什么Asynchrony不是“把方法放到后台线程”
Asynchrony允许operation在等待外部completion时不占用当前call stack/thread,并在完成后继续logical flow。
可核验证据
以明确的 LangVersion 与目标框架构建「为什么Asynchrony不是“把方法放到后台线程”」的正反案例,并用编译诊断、生成 IL、运行轨迹或分配数据核对实际边界。
学完《Chapter 5. Writing asynchronous code》后,应能从输入和前置条件推导状态变化,并用可重复的构建、运行或边界测试证明结果。
失效—证据矩阵
Chapter 5. Writing asynchronous code:失效与核验
为什么Asynchrony不是“把方法放到后台线程”
典型失效
若解释「为什么Asynchrony不是“把方法放到后台线程”」时混淆语言规范、编译器降级、运行时和类库责任,版本变化后就会把实现细节误当作 C# 语义保证。
核验证据
以明确的 LangVersion 与目标框架构建「为什么Asynchrony不是“把方法放到后台线程”」的正反案例,并用编译诊断、生成 IL、运行轨迹或分配数据核对实际边界。
Introducing asynchronous func…
典型失效
若解释「Introducing asynchronous func…」时混淆语言规范、编译器降级、运行时和类库责任,版本变化后就会把实现细节误当作 C# 语义保证。
核验证据
以明确的 LangVersion 与目标框架构建「Introducing asynchronous func…」的正反案例,并用编译诊断、生成 IL、运行轨迹或分配数据核对实际边界。
Thinking about asynchrony
典型失效
若解释「Thinking about asynchrony」时混淆语言规范、编译器降级、运行时和类库责任,版本变化后就会把实现细节误当作 C# 语义保证。
核验证据
以明确的 LangVersion 与目标框架构建「Thinking about asynchrony」的正反案例,并用编译诊断、生成 IL、运行轨迹或分配数据核对实际边界。
为什么Asynchrony不是“把方法放到后台线程”
Asynchrony允许operation在等待外部completion时不占用当前call stack/thread,并在完成后继续logical flow。async/await把continuation写成顺序source,但没有自动创建thread,也没有自动parallelize independent work。真正资源来自awaitable producer:I/O completion、timer、queue或已经运行的task。
先预测:调用async method是否立即返回;每个await是否thread switch;连续两次await是否并发;async void fault是否存进可观察Task。答案都是否。
↡从async method进入、同步前缀、可能suspend、continuation到最终task completion的可观察时间线。Introducing asynchronous functions
Async function让method/lambda包含await并返回task-like completion。Caller获得Task后可以await、compose、cancel source或observe fault。它比callback参数更清楚地把completion作为value,却仍要求ownership:谁必须await,谁可fire-and-forget,process shutdown是否等待。
Thinking about asynchrony
先区分I/O-bound与CPU-bound。I/O async依赖nonblocking underlying API,等待期间通常无需worker thread;CPU work要并行则需要scheduler/thread resources,不能仅加async。Latency、throughput与responsiveness目标不同,UI不阻塞不等于server吞吐变高。
Structured flow要求child operations不逃逸parent lifetime。Independent tasks可并发,但有fan-out bound、cancellation与all-fault observation;dependent tasks按顺序await。把所有work塞进Task.Run会隐藏resource budget与真正blocking API。
Async method declarations
常见return是Task、Task<T>、event-only void以及C# 7 custom task-like。Async modifier不属于signature identity,caller只看到return type。Argument validation在进入async state machine的时机需要谨慎;希望同步fail-fast可用non-async wrapper验证后调用private async core。
Await expressions
Await pattern寻找GetAwaiter、IsCompleted、OnCompleted/UnsafeOnCompleted与GetResult。Already-completed awaiter走fast path继续inline;pending时保存state、注册continuation并返回caller。GetResult既取result也传播fault/cancellation。
public async Task<string> DownloadAsync(Uri uri, CancellationToken token)
{
using var response = await client.GetAsync(uri, token);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync(token);
}切换declaration、completed/pending await与return wrapping
Wrapping of return values
在async Task<T>中写return value,builder把T用于完成外层Task;return Task<T>不合法,因为compiler负责wrapping。Throw不会从大多数call site同步逃出,而是fault返回task(同步wrapper验证除外)。Caller await时以原exception语义重新抛出。
Asynchronous method flow
Control flow跨await被拆成states。Loops、try/catch/finally、using都需跨suspension保持locals与cleanup。Finally可包含await(受language规则约束),但cleanup仍要有cancellation policy,避免primary fault被secondary cleanup fault覆盖。
Multiple awaits sequential by source unless tasks先启动。await A(); await B();在A完成后才调用B;var a=A(); var b=B(); await Task.WhenAll(a,b);才允许overlap。WhenAll fault handling应观察所有task outcomes,而不是只记录await抛出的一个exception。
Asynchronous anonymous functions
Async lambda按target变为Func<..., Task>/Func<..., Task<T>>或Action。传给接受Action的overload可能成为async void,caller无法await且fault走environment。API应提供Task-returning delegate overload,call site必要时显式target type。
Func<CancellationToken, Task> refresh = async token =>
{
var snapshot = await LoadAsync(token);
await PublishAsync(snapshot, token);
};切换async lambda、void、custom task与Main
Custom task types in C# 7
C# 7允许带AsyncMethodBuilder的task-like return type。它可针对特殊synchronous completion或domain scheduler优化,但builder、awaiter、single/multiple consumption和exception semantics都需正确实现。大多数application不应自造;使用Task,只有profile与library-level expertise支持时才选择ValueTask/custom type。
Async main methods in C# 7.1
Main可返回Task/Task<int>,让entry point自然await startup pipeline并把int映射exit code。它不自动等待detached background tasks;host仍需拥有service lifetime、shutdown token与drain timeout。
Usage tips
Cancellation是cooperative contract:token一路传入真正operation,OperationCanceledException需与对应token关联,cleanup通常不应被同一已取消token立即打断。Timeout可由linked token/policy实现,但必须区分caller cancel与deadline。
Avoid sync-over-async,尤其单线程context上.Result等待captured continuation会形成cycle。Library是否用ConfigureAwait(false)取决于context contract与现代runtime;核心是不要依赖未知ambient context,并在UI boundary显式marshal state mutation。
ValueTask适合频繁同步完成且allocation已被量测的hot API;consumer通常只await一次,不随意多次await、缓存或并发组合,除非先AsTask。复杂semantic cost经常大于省下一次allocation。
var profileTask = LoadProfileAsync(userId, token);
var policyTask = LoadPolicyAsync(userId, token);
await Task.WhenAll(profileTask, policyTask);
return Build(profileTask.Result, policyTask.Result);切换sequential、concurrent、cancel、sync boundary与ValueTask
本章回顾:Async把Completion变成可组合Value
- Async method先同步执行,只有incomplete await才suspend;await不承诺new thread。
- Awaiter pattern、builder与Task分别连接producer completion、state machine和caller observation。
- Return/throw/cancel被wrapping为task state;async void失去可awaitcompletion,只限event boundary。
- Independent tasks需先启动才overlap,并受concurrency budget和all-fault observation约束。
- Cancellation、sync boundary、custom task/ValueTask都需要明确ownership与race tests。
练习
问题 1:为何两个连续await没有并发,如何安全并发?
问题 2:async lambda传给Action overload有什么风险?
问题 3:何时考虑ValueTask或custom task-like?
术语表
名词解释
本章出现的专业名词,用大白话再讲一遍。
- asynchronous flow contract
- return-value wrapping
- async completion ownership
- concurrency budget
- cooperative cancellation
原版目录概念补充核对
以下条目补齐官方目录中容易被示例主线掩盖的概念。它们不重复罗列目录,而是明确每项概念的机制、适用边界和验收证据。
Thinking about asynchrony:机制、边界与证据
Chapter 5. Writing asynchronous code中的Thinking about asynchrony涉及值的存储位置、复制语义与生命周期,语法简洁不代表没有别名或逃逸限制。准备可编译和应被编译器拒绝的样本,结合 IL、分配计数或地址/修改轨迹核对复制、别名与边界。
Async method declarations:机制、边界与证据
Chapter 5. Writing asynchronous code中的Async method declarations必须分清语言规范、编译器实现、运行时行为与基础类库 API 四层责任。固定 C# 语言版本和目标框架,用正向/负向编译案例、必要的 IL 或运行轨迹以及版本对照验证结论。
Await expressions:机制、边界与证据
Chapter 5. Writing asynchronous code中的Await expressions必须分清语言规范、编译器实现、运行时行为与基础类库 API 四层责任。固定 C# 语言版本和目标框架,用正向/负向编译案例、必要的 IL 或运行轨迹以及版本对照验证结论。
Wrapping of return values:机制、边界与证据
Chapter 5. Writing asynchronous code中的Wrapping of return values涉及值的存储位置、复制语义与生命周期,语法简洁不代表没有别名或逃逸限制。准备可编译和应被编译器拒绝的样本,结合 IL、分配计数或地址/修改轨迹核对复制、别名与边界。
Asynchronous method flow:机制、边界与证据
Chapter 5. Writing asynchronous code中的Asynchronous method flow必须分清语言规范、编译器实现、运行时行为与基础类库 API 四层责任。固定 C# 语言版本和目标框架,用正向/负向编译案例、必要的 IL 或运行轨迹以及版本对照验证结论。
Asynchronous anonymous functions:机制、边界与证据
Chapter 5. Writing asynchronous code中的Asynchronous anonymous functions必须分清语言规范、编译器实现、运行时行为与基础类库 API 四层责任。固定 C# 语言版本和目标框架,用正向/负向编译案例、必要的 IL 或运行轨迹以及版本对照验证结论。
Custom task types in C# 7:机制、边界与证据
Chapter 5. Writing asynchronous code中的Custom task types in C# 7涉及值的存储位置、复制语义与生命周期,语法简洁不代表没有别名或逃逸限制。准备可编译和应被编译器拒绝的样本,结合 IL、分配计数或地址/修改轨迹核对复制、别名与边界。
Async main methods in C# 7.1:机制、边界与证据
Chapter 5. Writing asynchronous code中的Async main methods in C# 7.1涉及值的存储位置、复制语义与生命周期,语法简洁不代表没有别名或逃逸限制。准备可编译和应被编译器拒绝的样本,结合 IL、分配计数或地址/修改轨迹核对复制、别名与边界。