大多数浏览器和
Developer App 均支持流媒体播放。
-
超越结构化并发的基础
一切都与任务树有关:了解结构化并发如何帮助你的 App 管理自动任务取消、任务优先级传播以及有用的任务局部值模式。学习如何通过有用的模式和最新的任务组 API 在 App 中管理资源。我们将向你展示如何利用任务树和任务局部值的强大功能来深入了解分布式系统。在观看之前,请查看 WWDC21 的“幕后的 Swift 并发”和“探索 Swift 中的结构化并发”来复习 Swift 并发和结构化并发的基础知识。
章节
- 0:56 - Structured concurrency
- 3:11 - Task tree
- 3:44 - Task cancellation
- 5:26 - withTaskCancellationHandler
- 8:36 - Task priority
- 10:23 - Patterns with task groups
- 11:27 - Limiting concurrent tasks in TaskGroups
- 12:22 - DiscardingTaskGroup
- 13:53 - Task-local values
- 16:58 - swift-log
- 17:19 - MetadataProvider
- 18:58 - Task traces
- 19:46 - Swift-Distributed-Tracing
- 20:42 - Instrumenting distributed computations
- 23:38 - Wrap-up
资源
相关视频
WWDC23
WWDC22
WWDC21
-
下载
♪ ♪
Evan:大家好 我叫 Evan 今天 我们要 超越结构化并发的基础 探索结构化任务 如何简化实现有用的行为 在我们开始之前 如果你是新手 或者想复习结构化并发 可以随时查看 WWDC 2021 的 “探索 Swift 中的结构化并发” 和“幕后的 Swift 并发”讲座 今天 我们将回顾任务层次结构 以及它如何实现自动任务取消、 优先级传播 和有用的任务局部值行为 然后 我们将介绍一些任务组模式 以帮助管理资源使用情况 最后 我们将学习 如何将这些功能结合 来促进在服务器环境中的 任务分析和跟踪 结构化并发使你能够 使用定义明确的点 对并发代码进行推理 在这些点上执行分支并同时运行 并执行结果重新汇合 这类似于“if”代码块 和“for”循环定义同步代码中 控制流行为的方式 并发执行可以 在你使用“async let”、 任务组 或创建任务或独立任务时触发 而其结果 将在以“await”标记的悬点 重新加入当前执行 并非所有的任务都是结构化的 结构化任务是使用 “async let”和任务组来创建的 而非结构化任务则是使用 Task 和 Task.detached 创建的 结构化任务的生命周期 延续到声明它们的范围结束 就像局部变量一样 在其范围结束时 它们会自动取消 因此 可以清楚地知道任务的生命周期 如果可能的话 请优先选择结构化 Task 稍后讨论的结构化并发的好处 并不总适用于非结构化的任务 在深入研究代码之前 让我们先举一个具体的例子 假设我们有一个厨房 里面有多名厨师在准备汤 汤的制备需要多个步骤 厨师们必须切好食材 腌制鸡肉 将汤煮沸 最后把汤做好 就可以直接上桌 其中有些任务可以并行进行 而其他任务 必须按照特定的顺序完成 让我们来看看 如何用代码表达这一点 现在 我们先重点关注 makeSoup 函数 你可能会发现自己 创建了一些非结构化 Task 来为函数添加并发 并在必要时等待它们的值 虽然这样可以表达哪些任务 可以并发运行 哪些不能 但我们并不推荐在 Swift 中 这样使用并发 下面是使用结构化并发 表达的同一个函数 由于我们知道要创建的子任务数量 我们可以使用 方便的“async let”语法 这些任务与它们的父任务 形成了结构化关系 我们很快会谈到为什么这很重要 makeSoup 调用了许多异步函数 其中一个是“chopIngredients” 它接收食材列表 并使用任务组来并发地切所有食材 现在我们已经熟悉了 makeSoup 让我们来看看任务的层次结构 子任务会由彩色框表示 而箭头则从父任务指向子任务 makeSoup 有三个子任务 分别是切食材、 腌制鸡肉和煮汤 chopIngredients 使用任务组 为每种食材创建一个子任务 如果我们有三种食材 它就会创建三个子任务 这种父子层次结构形成了 一种树型结构 即任务树 现在我们已经介绍了任务树 让我们来看看 它对我们的代码有什么好处 任务取消用于向任务发出信号 表明 App 不再需要 任务的结果 并且任务应该停止并返回部分结果 或抛出错误 在本例中 我们有时会需要停止制作 一份汤品订单 比如顾客离开了、 决定要点其他菜品 或者已经到了关门时间 是什么导致了任务取消呢? 结构化任务在超出其范围时 会被隐式取消 不过你可以在任务组上 调用“cancelAll”方法 来取消当前所有 活跃的子任务和未来的子任务 非结构化任务可以通过 “cancel”函数显式取消 取消父任务 将导致所有子任务被取消 任务取消是协作性的 因此子任务不会立即停止 它只会在该任务上设置 “isCancelled”标志 任务取消的实际操作 是在你的代码中完成的 取消操作是一个竞争过程 如果任务在我们检查之前被取消 “makeSoup”会抛出 “SoupCancellationError” 如果任务在 guard 执行后取消 程序将继续准备汤 如果我们要抛出取消错误 而不是返回部分结果 我们可以调用 “Task.checkCancellation” 它会在任务被取消时抛出 “CancellationError” 在开始任何耗费资源的工作之前 你应该检查任务取消状态 以验证结果是否仍然是必要的 这很重要 取消检查是同步进行的 因此任何需要 响应取消的函数 无论是异步还是同步的 都应该在继续之前 接受任务取消状态检查 当任务运行时 使用“isCancelled” 或“checkCancellation” 进行取消轮询非常有用 但有时你可能需要在任务暂停 且没有代码运行时响应取消操作 比如在实现异步序列时 这时候“withTaskCancellationHandler” 就派上了用场 让我们引入一个轮班函数 厨师应该在订单到来后制作汤 直到轮班结束 而结束的信号是任务取消 在一个取消场景中 异步 for 循环 在被取消之前获得了一个新订单 “makeSoup”函数 会像我们之前定义的那样 处理取消 并抛出一个错误 在另一种场景 取消可能发生在 任务被挂起 等待下一个订单的时候 这种情况更有意思 因为任务并未运行 所以我们无法显式轮询取消事件 相反 我们必须使用取消处理程序 来检测取消事件 并打破异步 for 循环 订单是由异步序列生成的 异步序列 由 AsyncIterator 驱动 它定义了一个异步“next”函数 与同步迭代器类似 “next”函数返回序列中的 下一个元素 或者返回 nil 表示我们已经到达序列的末尾 许多异步序列使用状态机来实现 我们用它来停止正在运行的序列 在这个例子中 当“isRunning”为 true 时 序列应该继续发出订单 一旦任务被取消 我们需要指示 序列已完成并应该关闭 我们可以通过在序列状态机上 同步调用“cancel”函数来实现 请注意 由于取消处理程序会立即运行 状态机成为 取消处理程序和主体之间共享的 可变状态 可以并发运行 我们需要保护我们的状态机 虽然 actor 对于 保护封装状态非常有用 但我们想要修改和读取 状态机上的单个属性 因此 actor 并不是最合适的工具 此外 我们无法保证 操作在 actor 上运行的顺序 因此我们无法确保 我们的取消将首先运行 我们需要其他的解决方案 我决定使用 Swift Atomics 包的原子来操作 但我们也可以使用调度队列或锁 这些机制允许我们同步共享状态 避免竞争条件 同时允许我们 取消正在运行的状态机 而无需在取消处理程序中 引入非结构化任务 任务树会自动传播任务取消信息 我们无需担心取消令牌和同步 而是让 Swift 运行时 安全地处理它 请记住 取消并不会停止任务的运行 它只是向任务发出信号 告知它已被取消 应尽快停止运行 需要由你的代码来检查是否已取消 接下来 让我们看看结构化任务树 如何帮助传播优先级 并避免优先级反转 首先 什么是优先级 我们为什么要关心它? 你可以用优先级向系统传达 给定任务的紧急程度 某些任务 比如对按钮按下进行响应 需要立即运行 否则 App 会显示卡顿 与此同时 其他任务 比如从服务器预先获取内容 则可以在后台运行而不被用户察觉 其次 什么是优先级反转? 当高优先级任务正在等待 低优先级任务的结果时 就会发生优先级反转 默认情况下 子任务 会继承其父任务的优先级 所以假设 makeSoup 正在以中等优先级的任务运行 那么其所有子任务 也将以中等优先级运行 假设有一位 VIP 顾客 来到餐厅想要喝汤 我们将为这位客人的汤设置更高的 优先级 以确保我们得到好评 当客人在等待汤时 所有子任务的优先级都会提升 确保没有高优先级任务 在等待低优先级任务 从而避免了优先级反转的问题 等待具有较高优先级的任务结果 将提升任务树中 所有子任务的优先级 请注意 等待任务组的下一个结果 会提升组中所有子任务的优先级 因为我们不知道 哪个子任务最有可能先完成 并发运行时 使用优先队列来安排任务 因此优先级较高的任务 会在优先级较低的任务之前运行 该任务会在其剩余生命周期内 保持提升后的优先级 优先级提升是无法撤销的 我们快速地送上了汤 满足了我们的 VIP 顾客 并获得了好评 所以我们餐厅的人气变得更高了 我们希望确保所有资源 都得到了有效利用 我们还注意到 我们创建了很多切菜任务 让我们来看一些用于 管理任务组并发的有用模式 我们的空间只能容纳有限的切菜板 如果我们同时切太多食材 就会没有足够的空间 来完成其他任务 所以我们需要限制 同时切的食材的数量 回到我们的代码 我们想要研究创建切菜任务的循环 我们用一个循环代替原来的 对每种食材的循环 该循环从最大数量的切菜任务开始 我们希望收集结果的循环 每次在前面的任务完成后 开始一个新的任务 新循环会等待一个 正在运行中的任务完成 并在仍有需要切的食材时 添加一个新任务来切下一种食材 让我们对这个想法进行提炼 来更清楚地了解这个模式 初始循环会创建 最大数量的并发任务 确保我们不会创建太多 一旦运行的任务数达到上限 我们就等待其中一个任务完成 任务完成后 如果我们没有遇到停止条件 我们将创建一个新任务来继续推进 这限制了组内并发任务的数量 因为在之前的任务完成之前 我们不会启动新的工作 我们之前谈到了厨师们轮班工作 并使用取消操作 来表示他们的轮班结束 这是处理轮班的 Kitchen Service 代码 每个厨师开始轮班时 都会执行一项单独的任务 一旦厨师们开始工作 我们就启动一个计时器 当计时器结束时 我们就取消所有正在进行的轮班 请注意 这些任务都不会返回值 withDiscardingTaskGroup API 是 Swift 5.9 中的新增功能 丢弃任务组不会保留 已完成子任务的结果 任务使用的资源 会在任务完成后立即释放 我们可以更改运行方法 以使用丢弃任务组 丢弃任务组会自动清理其子任务 因此无需 显式取消任务组并进行清理 丢弃任务组还具有 自动取消同级任务的功能 如果任何子任务抛出错误 所有剩余的任务都将自动取消 这非常适合我们的用例 我们可以在轮班结束时 抛出“TimeToCloseError” 它会自动结束所有厨师的轮班 新的丢弃任务组会在任务结束时 自动释放资源 与普通的任务组不同 普通任务组需要开发者来收集结果 当你有许多不需要 返回任何结果的任务时 例如处理一系列请求时 这有助于减少内存消耗 在某些情况下 你可能希望从任务组中返回一个值 但同时也想限制并发任务的数量 我们介绍了一种通用模式 利用一个任务的完成 来启动另一个任务 从而避免任务激增 我们做汤的效率 比以往任何时候都高 但我们仍然需要扩大规模 是时候将生产转移到服务器上了 随之而来的挑战 是追踪订单的处理过程 任务局部值可以在这发挥作用 任务局部值是与给定任务 或者更准确地说 与任务层次结构关联的一段数据 它类似于全局变量 但绑定到 任务局部值的值 只能从当前任务层次结构中访问 任务局部值通过 “TaskLocal”属性包装器 声明为静态属性 将任务局部设为可选 是一个很好的做法 任何未设置值的任务 都需要返回一个默认值 该值可以很容易地 用 nil 可选项来表示 未绑定的任务局部包含其默认值 在我们的示例中 我们有一个可选的 String 所以它是 nil 并且没有与当前任务关联的厨师 任务局部值不能显式赋值 而必须绑定到特定范围 绑定将在该范围的持续时间内有效 并在范围结束时恢复为初始值 回到任务树 每个任务都有一个 任务局部值的相关位置 在做汤之前 我们将名称“Sakura”绑定到 “cook”任务局部变量 只有 makeSoup 存储了绑定的值 子任务在其任务局部存储中 没有保存任何值 查找绑定到任务局部变量的值 需要递归遍历每个父级 直到找到具有该值的任务 如果我们找到了绑定该值的任务 任务局部将采用该值 如果我们到达了 root 即任务没有父任务 那么任务局部未绑定 我们将获得原始默认值 Swift 运行时经过优化 可以更快地运行这些查询 我们不需要遍历任务树 而是直接通过键值 找到所需任务的引用 任务树的递归性质使其非常适合 在不丢失之前的值的情况下隐藏值 假设我们想要 追踪制作汤的当前步骤 我们可以将“step”变量绑定到 “'makeSoup”中的“soup” 然后将其重新绑定到 “chopIngredients”中的“chop” 在 chopIngredients 中 绑定的值将会隐藏之前的值 直到我们从 chopIngredients 返回 我们会在那里看到初始值 借助视频编辑的神奇力量 我们将服务转移了到云端 以满足对汤的需求 我们依然保留着 原有的制作汤的功能 只不过现在它在服务器上 我们需要在订单 通过系统时对其进行观察 以确保它们能够及时完成 并监控意外故障 由于服务器环境 能够同时处理多个请求 因此我们需要包含 允许我们追踪给定订单的信息 手动录入既重复又繁琐 这会导致 不易察觉的漏洞和拼写错误 哦不 我不小心录入了 整个订单而不仅仅是订单 ID 让我们来看看如何使用任务局部值 让日志记录更可靠 在 Apple 设备上 你会想要继续 直接使用 OSLog API 但随着 App 的一部分迁移到云端 你将需要其他解决方案 SwiftLog 是一个日志记录 API 包 它具有多个后端实现 允许你添加适合你需求的 日志记录后端 而无需更改服务器 MetadataProvider 是 SwiftLog 1.5 中的新 API 执行元数据提供程序 可以轻松抽象你的日志记录逻辑 以确保 你发出有关相关值的一致信息 元数据提供程序 使用类似字典的结构 将名称映射到正在记录的值 我们希望自动记录 orderID 任务局部变量 因此我们来检查它是否已定义 如果已定义 则将其添加到字典中 多个库可以定义 自己的元数据提供程序 来查找特定库的信息 因此 MetadataProvider 定义了一个“multiplex”函数 该函数采用多个元数据提供程序 并将它们组合成单个对象 一旦我们有了元数据提供程序 我们就可以使用该提供程序 初始化日志系统 然后就可以开始录入了 日志自动包含 元数据提供程序中指定的信息 因此我们无需担心 将其包含在日志消息中 日志会显示订单 0 进入厨房 以及我们的厨师接收该订单的位置 元数据提供程序中的值 在日志中清晰列出 使你可以更轻松地 在制作汤的过程中追踪订单 任务局部值允许你 将信息附加到任务层次结构 除了分离的任务 所有任务 都会从当前任务继承任务局部值 它们在给定的范围中 被绑定到一个特定的任务树 为你提供了初级构建块 以通过任务层次结构 传播附加上下文信息 现在 我们将使用 任务层次结构及其提供的工具 来跟踪和分析并发分布式系统 在 Apple 平台上处理并发时 Instruments 是你的好帮手 Swift 并发工具 可以让你深入了解 结构化任务之间的关系 要了解更多信息 请查看 “Swift 并发的可视化和优化”讲座 此外 Instruments 还在 “在 Instruments 中分析 HTTP Traffic” 讲座中引入了 新的 HTTP 流量工具 HTTP 流量分析器 仅显示本地事件的跟踪情况 在等待服务器响应时 评测只会显示一个灰色框 因此我们需要更多信息来了解 如何改进服务器性能 让我来介绍新的 Swift Distributed Tracing 包 任务树非常适用于 在单个任务层次结构中 管理子任务 分布式追踪可以让你在多个系统中 利用任务树的优势 来深入了解 性能特征和任务关系 Swift Distributed Tracing 包执行了 OpenTelemetry 协议 因此现有的追踪解决方案 如 Zipkin 和 Jaeger 可以开箱即用 我们开发 Swift Distributed Tracing 是为了填补 Xcode Instruments 中不透明的 “waiting for response”部分 提供服务器中正在发生的详细信息 我们需要检测我们的服务器代码 以确定我们需要关注的地方 分布式跟踪 与本地跟踪进程有些不同 我们不再按每个函数获取跟踪 而是使用“withSpan”API 为代码添加 span Span 允许我们 为代码区域指定名称 并将其报告到跟踪系统中 Span 不需要覆盖整个函数 它们可以更详细地 展示给定函数的特定部分 withSpan 为我们的任务注释了 额外的跟踪 ID 和其他元数据 从而让跟踪系统能够 将任务树合并成为单一的跟踪 跟踪系统拥有足够的信息 可以帮助你了解 任务层次结构 并了解 有关任务运行时性能特征的信息 span 名称会在跟踪 UI 中呈现 你应让它们简短且描述清楚 以便你可以轻松找到 特定 span 的信息 而不至于混淆 我们可以使用 span 属性 附加额外的元数据 这样我们就不会将 span 名称与订单 ID 混淆 在这里 我们用“#function”指令 替换了 span 名称 以自动将 span 名称 填充为函数名 并使用 span 属性将当前订单 ID 附加到 报告给追踪器的 span 信息中 跟踪系统在检查给定 span 时 通常会显示属性 大多数 span 都带有 HTTP 状态代码、 请求和响应大小、 开始和结束时间以及其他元数据 这让你可以更轻松地 追踪经过系统的信息 正如前一张幻灯片中所示 你可以定义自己的属性 要了解 利用 span 的更多示例 请查看 swift-distributed-tracing-extras 储存库 如果一个任务失败并抛出错误 这些信息也会显示在 span 中 并在跟踪系统中报告 由于 span 包含了任务的时间信息 和任务树中任务之间的关系 因此它有助于追踪时序竞争 导致的错误 并确定它们如何影响其他任务 我们已经讨论了跟踪系统 以及它如何使用跟踪 ID 重建任务树 以及如何将你自己的属性 附加到 span 但我们还没有开始 将其应用到分布式系统中 跟踪系统的美妙之处在于 你不需要进行其他额外的操作 如果我们将切菜服务 从厨房服务中独立出来 但保持代码不变 跟踪系统将自动捕捉到这些跟踪 并将它们与分布式系统 中的不同机器关联起来 跟踪视图将显示 这些 span 在不同的机器上运行 但其余部分都是相同的 分布式跟踪在系统的 所有部分都采用跟踪时 效果最佳 包括 HTTP 客户端、 服务器和其他 RPC 系统 Swift Distributed Tracing 利用基于任务树构建的 任务局部值来自动传播 所有必要的信息 以生成可靠的跨节点追踪 结构化任务可以揭开 你的并发系统的奥秘 为你提供自动取消操作、 自动传播优先级信息 以及方便追踪复杂的 分布式工作负载的工具 这些工具能起作用 多亏了 Swift 中 并发的结构化本质 我希望本讲座能让你 对结构化并发产生兴趣 并希望你在使用非结构化的 替代方案之前 先使用结构化的任务 感谢你的观看! 我已经等不及想看到 你将使用结构化并发 创造出哪些其他有用的模式 嗯 汤来了! ♪ ♪
-
-
2:27 - Unstructured concurrency
func makeSoup(order: Order) async throws -> Soup { let boilingPot = Task { try await stove.boilBroth() } let choppedIngredients = Task { try await chopIngredients(order.ingredients) } let meat = Task { await marinate(meat: .chicken) } let soup = await Soup(meat: meat.value, ingredients: choppedIngredients.value) return await stove.cook(pot: boilingPot.value, soup: soup, duration: .minutes(10)) }
-
2:42 - Structured concurrency
func makeSoup(order: Order) async throws -> Soup { async let pot = stove.boilBroth() async let choppedIngredients = chopIngredients(order.ingredients) async let meat = marinate(meat: .chicken) let soup = try await Soup(meat: meat, ingredients: choppedIngredients) return try await stove.cook(pot: pot, soup: soup, duration: .minutes(10)) }
-
3:00 - Structured concurrency
func chopIngredients(_ ingredients: [any Ingredient]) async -> [any ChoppedIngredient] { return await withTaskGroup(of: (ChoppedIngredient?).self, returning: [any ChoppedIngredient].self) { group in // Concurrently chop ingredients for ingredient in ingredients { group.addTask { await chop(ingredient) } } // Collect chopped vegetables var choppedIngredients: [any ChoppedIngredient] = [] for await choppedIngredient in group { if choppedIngredient != nil { choppedIngredients.append(choppedIngredient!) } } return choppedIngredients } }
-
4:32 - Task cancellation
func makeSoup(order: Order) async throws -> Soup { async let pot = stove.boilBroth() guard !Task.isCancelled else { throw SoupCancellationError() } async let choppedIngredients = chopIngredients(order.ingredients) async let meat = marinate(meat: .chicken) let soup = try await Soup(meat: meat, ingredients: choppedIngredients) return try await stove.cook(pot: pot, soup: soup, duration: .minutes(10)) }
-
4:58 - Task cancellation
func chopIngredients(_ ingredients: [any Ingredient]) async throws -> [any ChoppedIngredient] { return try await withThrowingTaskGroup(of: (ChoppedIngredient?).self, returning: [any ChoppedIngredient].self) { group in try Task.checkCancellation() // Concurrently chop ingredients for ingredient in ingredients { group.addTask { await chop(ingredient) } } // Collect chopped vegetables var choppedIngredients: [any ChoppedIngredient] = [] for try await choppedIngredient in group { if let choppedIngredient { choppedIngredients.append(choppedIngredient) } } return choppedIngredients } }
-
5:47 - Cancellation and async sequences
actor Cook { func handleShift<Orders>(orders: Orders) async throws where Orders: AsyncSequence, Orders.Element == Order { for try await order in orders { let soup = try await makeSoup(order) // ... } } }
-
6:41 - Cancellation and async sequences
public func next() async -> Order? { return await withTaskCancellationHandler { let result = await kitchen.generateOrder() guard state.isRunning else { return nil } return result } onCancel: { state.cancel() } }
-
7:40 - AsyncSequence state machine
private final class OrderState: Sendable { let protectedIsRunning = ManagedAtomic<Bool>(true) var isRunning: Bool { get { protectedIsRunning.load(ordering: .acquiring) } set { protectedIsRunning.store(newValue, ordering: .relaxed) } } func cancel() { isRunning = false } }
-
10:55 - Limiting concurrency with TaskGroups
func chopIngredients(_ ingredients: [any Ingredient]) async -> [any ChoppedIngredient] { return await withTaskGroup(of: (ChoppedIngredient?).self, returning: [any ChoppedIngredient].self) { group in // Concurrently chop ingredients for ingredient in ingredients { group.addTask { await chop(ingredient) } } // Collect chopped vegetables var choppedIngredients: [any ChoppedIngredient] = [] for await choppedIngredient in group { if let choppedIngredient { choppedIngredients.append(choppedIngredient) } } return choppedIngredients } }
-
11:01 - Limiting concurrency with TaskGroups
func chopIngredients(_ ingredients: [any Ingredient]) async -> [any ChoppedIngredient] { return await withTaskGroup(of: (ChoppedIngredient?).self, returning: [any ChoppedIngredient].self) { group in // Concurrently chop ingredients let maxChopTasks = min(3, ingredients.count) for ingredientIndex in 0..<maxChopTasks { group.addTask { await chop(ingredients[ingredientIndex]) } } // Collect chopped vegetables var choppedIngredients: [any ChoppedIngredient] = [] for await choppedIngredient in group { if let choppedIngredient { choppedIngredients.append(choppedIngredient) } } return choppedIngredients } }
-
11:17 - Limiting concurrency with TaskGroups
func chopIngredients(_ ingredients: [any Ingredient]) async -> [any ChoppedIngredient] { return await withTaskGroup(of: (ChoppedIngredient?).self, returning: [any ChoppedIngredient].self) { group in // Concurrently chop ingredients let maxChopTasks = min(3, ingredients.count) for ingredientIndex in 0..<maxChopTasks { group.addTask { await chop(ingredients[ingredientIndex]) } } // Collect chopped vegetables var choppedIngredients: [any ChoppedIngredient] = [] var nextIngredientIndex = maxChopTasks for await choppedIngredient in group { if nextIngredientIndex < ingredients.count { group.addTask { await chop(ingredients[nextIngredientIndex]) } nextIngredientIndex += 1 } if let choppedIngredient { choppedIngredients.append(choppedIngredient) } } return choppedIngredients } }
-
11:26 - Limiting concurrency with TaskGroups
withTaskGroup(of: Something.self) { group in for _ in 0..<maxConcurrentTasks { group.addTask { } } while let <partial result> = await group.next() { if !shouldStop { group.addTask { } } } }
-
11:56 - Kitchen Service
func run() async throws { try await withThrowingTaskGroup(of: Void.self) { group in for cook in staff.keys { group.addTask { try await cook.handleShift() } } group.addTask { // keep the restaurant going until closing time try await Task.sleep(for: shiftDuration) } try await group.next() // cancel all ongoing shifts group.cancelAll() } }
-
12:41 - Introducing DiscardingTaskGroups
func run() async throws { try await withThrowingDiscardingTaskGroup { group in for cook in staff.keys { group.addTask { try await cook.handleShift() } } group.addTask { // keep the restaurant going until closing time try await Task.sleep(for: shiftDuration) throw TimeToCloseError() } } }
-
14:10 - TaskLocal values
actor Kitchen { @TaskLocal static var orderID: Int? @TaskLocal static var cook: String? func logStatus() { print("Current cook: \(Kitchen.cook ?? "none")") } } let kitchen = Kitchen() await kitchen.logStatus() await Kitchen.$cook.withValue("Sakura") { await kitchen.logStatus() } await kitchen.logStatus()
-
16:17 - Logging
func makeSoup(order: Order) async throws -> Soup { log.debug("Preparing dinner", [ "cook": "\(self.name)", "order-id": "\(order.id)", "vegetable": "\(vegetable)", ]) // ... } func chopVegetables(order: Order) async throws -> [Vegetable] { log.debug("Chopping ingredients", [ "cook": "\(self.name)", "order-id": "\(order.id)", "vegetable": "\(vegetable)", ]) async let choppedCarrot = try chop(.carrot) async let choppedPotato = try chop(.potato) return try await [choppedCarrot, choppedPotato] } func chop(_ vegetable: Vegetable, order: Order) async throws -> Vegetable { log.debug("Chopping vegetable", [ "cook": "\(self.name)", "order-id": "\(order)", "vegetable": "\(vegetable)", ]) // ... }
-
17:33 - MetadataProvider in action
let orderMetadataProvider = Logger.MetadataProvider { var metadata: Logger.Metadata = [:] if let orderID = Kitchen.orderID { metadata["orderID"] = "\(orderID)" } return metadata }
-
17:50 - MetadataProvider in action
let orderMetadataProvider = Logger.MetadataProvider { var metadata: Logger.Metadata = [:] if let orderID = Kitchen.orderID { metadata["orderID"] = "\(orderID)" } return metadata } let chefMetadataProvider = Logger.MetadataProvider { var metadata: Logger.Metadata = [:] if let chef = Kitchen.chef { metadata["chef"] = "\(chef)" } return metadata } let metadataProvider = Logger.MetadataProvider.multiplex([orderMetadataProvider, chefMetadataProvider]) LoggingSystem.bootstrap(StreamLogHandler.standardOutput, metadataProvider: metadataProvider) let logger = Logger(label: "KitchenService")
-
18:13 - Logging with metadata providers
func makeSoup(order: Order) async throws -> Soup { logger.info("Preparing soup order") async let pot = stove.boilBroth() async let choppedIngredients = chopIngredients(order.ingredients) async let meat = marinate(meat: .chicken) let soup = try await Soup(meat: meat, ingredients: choppedIngredients) return try await stove.cook(pot: pot, soup: soup, duration: .minutes(10)) }
-
20:30 - Profile server-side execution
func makeSoup(order: Order) async throws -> Soup { try await withSpan("makeSoup(\(order.id)") { span in async let pot = stove.boilWater() async let choppedIngredients = chopIngredients(order.ingredients) async let meat = marinate(meat: .chicken) let soup = try await Soup(meat: meat, ingredients: choppedIngredients) return try await stove.cook(pot: pot, soup: soup, duration: .minutes(10)) } }
-
21:36 - Profiling server-side execution
func makeSoup(order: Order) async throws -> Soup { try await withSpan(#function) { span in span.attributes["kitchen.order.id"] = order.id async let pot = stove.boilWater() async let choppedIngredients = chopIngredients(order.ingredients) async let meat = marinate(meat: .chicken) let soup = try await Soup(meat: meat, ingredients: choppedIngredients) return try await stove.cook(pot: pot, soup: soup, duration: .minutes(10)) } }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。