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. 1

    为什么Asynchrony不是“把方法放到后台线程”

    Asynchrony允许operation在等待外部completion时不占用当前call stack/thread,并在完成后继续logical flow。

  2. 2

    Introducing asynchronous func…

    Async function让method/lambda包含await并返回task-like completion。Caller获得Task后可以await、compose、cancel source或observe fault。它比callback参数更清楚地把completion作为value…

  3. 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。答案都是否。

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是TaskTask<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寻找GetAwaiterIsCompletedOnCompleted/UnsafeOnCompletedGetResult。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);
}
分步1 / 3

切换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);
};
分步1 / 3

切换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);
分步1 / 3

切换sequential、concurrent、cancel、sync boundary与ValueTask

本章回顾:Async把Completion变成可组合Value

  1. Async method先同步执行,只有incomplete await才suspend;await不承诺new thread。
  2. Awaiter pattern、builder与Task分别连接producer completion、state machine和caller observation。
  3. Return/throw/cancel被wrapping为task state;async void失去可awaitcompletion,只限event boundary。
  4. Independent tasks需先启动才overlap,并受concurrency budget和all-fault observation约束。
  5. 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、分配计数或地址/修改轨迹核对复制、别名与边界。

资料与写作方式声明

本章以C# in Depth, Fourth Edition, Chapter 5: Writing asynchronous code权威目录界定学习范围,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

原作版权归作者与出版社所有;本站原创教学结构与表述仅供学习交流。

讨论

评论区加载中…