大多数浏览器和
Developer App 均支持流媒体播放。
-
探索 Swift 中的结构化并发
当您的代码需要与其他代码同时运行时,为作业选择正确的工具很重要。我们将向您介绍可在 Swift 中创建的不同类型的并发任务,向您展示如何创建任务组,以及了解如何取消正在进行的任务。当您需要使用非结构化任务时,我们也会提供指导。为了能充分了解本节内容,我们建议首先观看“认识 Swift 中的 async/await”。
资源
相关视频
WWDC23
WWDC22
WWDC21
-
下载
♪♪ 你好 我是卡冯 而我的同事乔稍后会加入 Swift 5.5引入了一种编写 并行性程序的新方法 使用了一种称为结构化并行性的概念 结构化并行性背后的理念 来自结构化编程 它非常直观 你很少思考过它 但是思考它 就能帮助你理解 结构化并行性 所以让我们深入了解吧
在早期的计算机时代 程序很难读取 因为它们是按照指令序列编写的 允许控制流到处跳跃 而你今天看不到这些 因为语言使用结构化编程 使控制流更均匀 例如 if-then语句 使用结构化控制流 它指定嵌套的代码块 仅在从上到下移动时有条件地执行 在Swift中该块也尊重静态作用域 这意味着名称仅为可见 如果它们是在封闭块中被定义的话 这也意味着块中定义的 任何变量的生命周期 将在离开块时结束 因此 具有静态作用域的结构化编程 能使控制流和可变寿命易于理解
更普遍地说 结构化控制流可以排序 并自然地嵌套在一起 这使你可以从上到下读取整个程序 所以 这些是结构化编程的基础 正如你想象的那样 这很容易被认为是理所当然的 因为它对我们今天来说是如此直观 但是今天的程序 有着异步和并行性代码 还不能使用结构化编程 使该代码更易于编写 首先 让我们考虑结构化编程 如何使异步代码更简单 假设你需要从互联网 获取一堆图像并将它们 按顺序调整为缩略图 这段代码会异步地完成这项工作 接收识别图像的字符串集合 你会注意到这个函数 在调用时没有返回值 那是因为该函数将其结果或错误 传递给给定的完成处理程序 这种模式允许呼叫者 稍后才收到回覆 由于是那种模式 而此功能将不能 被用在错误处理的结构化控制流 那是因为只有处理从函数中 抛出的错误才有意义 而不是处理一个函数中的错误 此外 此模式可防止你使用循环 来处理每个缩略图 递归也是需要的 因为函数完成后运行的代码 必须嵌套在处理程序中 现在 让我们看一下之前的代码 但使用新的async/await 语法进行了重写 且基于结构化编程 我已经从函数中删除了 完成处理程序参数 相反地 它被注释为“async” 且“抛”进了其类型签名中 它还返回了一个值 而不是什么都没有 在函数主体中 我用“await”表示 发生了一个异步动作 且对于 在该操作之后运行的代码不需要嵌套 这意味着我现在可以遍历缩略图 来按顺序处理它们 我也可以抛出和捕获错误 编译程序会检查我没有忘记 要深入了解async/await 请查看讲座 《在Swift中认识async/await》
所以 这段代码很棒 但是如果你要为数千张图像 生成缩略图呢? 一次一张处理每个缩略图 可一点都不理想 另外 如果每个缩略图的尺寸 必须从另一个 URL下载 而不是固定大小怎么办? 现在这有个机会添加一些并行性 因此可以并行进行多次下载 你可以创建其他任务 来向程序添加并行性 任务是Swift的一个新功能 来与异步函数协同工作 这个任务提供了一个新的执行上下文 来运行异步代码 每个任务相对于 其他执行上下文 采取并行性的运行 在安全且高效的情况下 它们将被自动安排为并行运行 由于任务已深度集成到Swift中 因此编译程序可以帮助 防止一些并行性错误 另外 请记住 调用异步函数 不会为调用创建新任务 你明确地创建任务 Swift中有几种不同风格的任务 因为结构化并行性的重点在于 在灵活性和简单性之间平衡 因此 在本次讲座的剩余时间里 乔和我会介绍和讨论各种任务 以帮助你了解他们的权衡 让我们从这些任务中最简单的开始 它是用新的句法形式创建的 称为async-let绑定 为了帮助你理解这种新的句法形式 我想先分解一个 ordinarylet绑定的评估 有两个部分: 等号右侧的初始化表达式 和左侧的变量名称 在let之前或之后 可能还有其他语句 所以我也会在这包含这些语句 一旦Swift到达let绑定 它的初始化器将被评估 以产生一个值 在本例中 这意味着从 URL下载数据 这可能需要一段时间 数据下载完成后 在继续后面的语句之前 Swift会将该值绑定到变量名 请注意 这里只有一个执行流程 如同每个步骤的箭头所示 由于下载可能需要一段时间 你会希望程序开始下载数据 并继续做其他工作 直到真正需要数据为止 为此 你只需在现有的 let绑定前添加async一词即可 这将它变成一个并行性绑定 称为async-let 并行性绑定的评估 与顺序绑定有很大不同 所以让我们来了解它是如何工作的 我将从遇到绑定之前的 那个点开始说起 要评估并行性绑定 Swift首先会创建一个新的子任务 也是创建它的任务的子任务 因为每个任务都代表 程序的一个执行上下文 这一步会同时出现两支箭头 第一个箭头用于子任务 它将立即开始下载数据 第二个箭头指向父任务 会立即把变量结果 绑定到占位符值 该父任务与执行 上述语句的任务相同 当子任务并行性下载数据时 父任务继续执行 遵循并行性绑定的语句 但是在达到需要 结果的实际值的表达式时 父任务将等待子任务的完成 这会满足结果的占位符
在这个例子中 我们对URLSession的调用 也可能抛出一个错误 这意味着等待结果 可能会给我们一个错误 所以我需要写“try”来处理它 别担心 再次读取结果的值不会重新计算其值 现在你已经了解了 async-let的工作原理 你可以使用它为缩略图 获取代码添加并行性 我已经分解了一段先前的代码 该代码将单个图像 提取到其自己的函数中 这里的这个新功能也是下载数据 来自两个不同的URL 一个用于全尺寸图像本身 另一个用于元数据 其中包含最佳缩略图大小 请注意 使用顺序绑定 在let的右侧写上“try await” 因为那是会观察到错误或暂停的地方 为了让这两个下载 同时发生 在这两个 let的前面写上“async” 由于下载现在发生在 子任务中 你不用再 朝并行性绑定右侧 写“try await” 只有在使用并行性绑定的变量时 父任务才会观察到这些影响 所以在表达式读取元数据 和图像数据之前 你要写“try await” 另外 请注意 使用这些并行性绑定的变量 不需要方法调用或任何其他更改 这些变量的类型 与它们在顺序绑定中的类型相同
现在 我一直在谈论的这些子任务 实际上是称为任务树的 层次结构的一部分 这棵树不仅仅是一个实现细节 它是结构化并行性的重要组成部分 它会影响你任务的属性 例如取消、优先级 和任务局部变量 每当你从一个异步函数 调用另一个函数时 都会使用相同的任务来执行调用 因此 函数fetchOneThumbnail 继承了该任务的所有属性 当使用async-let 创建一个新的结构化任务时 它会成为当前函数 正在运行的任务的子任务 任务不是特定函数的子项 但它们的生命周期 可能限定在它的范围内 该树由每个父任务 与其子任务之间的链接组成 链接强制执行一条规则 说明父任务只能完成其工作 如果它的所有子任务都完成了 即使面对异常的控制流 这条规则也成立 这将阻止等待子任务 例如 在这段代码中 我会在图像数据任务之前 先等待元数据任务 如果第一个等待的任务 因抛出错误而完成 则fetchOneThumbnail函数 必须立即 通过抛出那个错误来退出 但是执行第二次 下载的任务会发生什么? 在异常退出时 Swift会自动将未等待的任务 标记为已取消 然后在退出函数之前 等待它完成 将任务标记为已取消不会停止该任务 它只是通知任务不再需要它的结果 事实上 当一个任务被取消时 所有从该任务继承的子任务 也会自动取消 所以如果URLSession的实现 创建了自己的结构化任务来下载图像 这些任务将被标记为取消 函数fetchOneThumbnail最后会退出 通过抛出错误的方式 就在它创建的所有结构化任务 直接或间接完成之后 这种保证是结构化并行性的基础 它通过帮助你管理任务的生命周期 来防止你意外泄漏任务 很像ARC如何自动管理 记忆的生命周期 到目前为止 我已经向你概述了 取消是如何传播的 但是任务什么时候结束呢? 如果任务处于重要事务的中间 或具有开放的网络连接 只是停止任务是不正确的 这就是为什么Swift中的 任务取消是协作的 你的代码必须明确检查取消 并以任何适当的方式结束执行 你可以从任何函数检查 当前任务的取消状态 无论它是否是异步的 这意味着你应该在实现API时 考虑取消 特别是如果它们涉及 长时间运行的计算 你的用户可能会从可以取消的任务中 调用你的代码 并且他们会期待 计算能尽快停止
为了了解使用协作取消是多么简单 让我们回到缩略图获取范例
在这里 我重写了原始函数 该函数被赋予了所有要获取的缩略图 以便它使用 fetchOneThumbnail函数 如果在取消的任务中调用此函数 我们不想通过创建无用的缩略图 来阻止我们的 app 所以我可以在每次循环迭代开始时 添加对checkCancellation的调用 如果当前任务已被取消 则此调用只会引发错误 如果更适合你的代码 你还可以 获取当前任务的取消状态作为布尔值 请注意 在此版本的函数中 我正在返回一个部分结果 一个只包含一些请求缩略图的字典 执行此操作时 你必须确保你的 API明确说明可能会返回部分结果 否则 任务取消可能会 为你的用户触发致命错误 因为他们的代码即使在取消期间 也需要完整的结果 到目前为止 你已经看到 async-let提供了 一种轻量级语法 用于在捕获结构化编程本质 同时向程序添加并行性 我想告诉你的下一种任务叫做组任务 它们比async-let 提供了更多的灵活性 不放弃结构化并行性的所有优良属性 正如我们之前看到的 当有固定数量的并行性可用时 async-let运行良好 让我们考虑一下 我之前讨论过的两个函数 对于循环中的每个缩略图ID 我们调用fetchOneThumbnail 对其进行处理 这正好创建了两个子任务 即使我们将该函数的主体 内联到这个循环中 并行性量不会改变 Async-let的作用域 类似于变量绑定 这意味着两个子任务必须在 下一次循环迭代开始之前完成 但是如果我们希望这个循环启动任务 以同时获取所有缩略图呢? 然后 并行性量不是静态已知的 因为它取决于数组中的ID数量 适合这种情况的工具是任务组 任务组是一种结构化并行性形式 旨在提供动态并行性量 你可以通过调用 withThrowingTaskGroup函数 来引入任务组 此函数为你提供了一个作用域组对象 来创建允许抛出错误的子任务 添加到组的任务 不能超过定义组的块的范围 由于我已将整个for循环放在块内 我现在可以使用该组 创建动态数量的任务 你可以通过调用其异步方法 在组中创建子任务 一旦添加到组中 子任务将以任何顺序 立即开始执行 当组对象超出范围时 将隐式等待其中的所有任务完成 这是我之前描述的任务树规则的结果 因为组任务也是结构化的 此时 我们已经实现了 我们想要的并行性: 每个调用fetchOneThumbnail的任务 它本身将使用async-let 创建另外两个任务 这是结构化并行性的 另一个很好的特性 你可以在组任务中使用async-let 或在async-let任务中创建任务组 并且在树中的并行性级别自然构成 现在 这段代码还没有准备好运行 如果我们尝试运行它 编译程序会提醒我们注意数据竞争问题 问题是我们试图从每个子任务中 将缩略图插入到单个字典中 这是增加程序并行性量时的常见错误 数据竞争是意外创建的 这本字典一次不能处理多个访问 如果两个子任务试图同时插入缩略图 这可能会导致崩溃或数据损坏 过去 你必须自己调查这些错误 但Swift提供了静态检查 来预先防止这些错误发生 每当你创建一个新任务时 该任务执行的工作都在称为 @Sendable闭包的新闭包类型中 @Sendable闭包的主体 被限制在其词法上下文中 不能捕获可变变量 因为这些变量可以在 任务启动后修改 这意味着你在任务中捕获的值 必须可以安全共享 例如 因为它们是值类型 如Int和String 或者因为它们是设计为 可从多个线程访问的对象 例如实现自己的同步的 演员、还有类别 我们有一个专门讨论这个主题的讲座 称为《使用Swift actor保护可变状态》 所以我推荐你去看看 为了避免我们范例中的数据竞争 你可以让每个子任务返回一个值 这个设计给了父任务 处理结果的唯一责任 在这种情况下 我指定每个子任务 必须返回一个包含字符串ID的元组 和缩略图的UIImage 然后 在每个子任务中 而不是直接写入字典 我让他们返回要处理的 父级的键值元组 父任务可以使用新的for-await循环 从每个子任务中遍历结果 for-await循环 从子任务中获取结果 按照完成顺序 因为这个循环是顺序运行的 所以父任务可以安全地 对字典添加每一对关键值组 这只是使用for-await循环的 一个例子 来访问异步值序列 如果你自己的类型符合 AsyncSequence协议 那么你也可以使用 for-await来遍历它们 你可以在《认识AsyncSequence》 讲座中找到更多信息 虽然任务组是结构化 并行性的一种形式 但任务树规则用于组任务 与async-let任务 的实现方式略有不同 假设在迭代该组的结果时 我遇到了一个以错误方式 完成的子任务 由于该错误被抛出组块之外 因此组中的所有任务都将被隐式取消 然后被等待 这就像async-let一样 当你的组通过了块的 正常退出超出范围时 就会出现差异 然后 取消不是隐式的 这种行为让你更容易 使用任务组表达fork-join模式 因为这些工作只会被等待 不会被取消 你还可以使用组的cancelAll方法 在退出块之前手动取消所有任务 请记住 无论你如何取消任务 取消都会自动沿树向下传播 Async-let和组任务是两种 Swift中提供 作用域结构化任务的任务 现在 我就交给乔 他会告诉你有关非结构化任务的信息 谢谢 卡冯 你好 我是乔 卡冯通过清晰的任务层次结构 向你展示了结构化并行性如何在 你将并行性添加到具有的程序时 简化错误传播、取消和其他簿记 但我们知道 在向程序中添加任务时 并不总是有层次结构 Swift还提供了 非结构化的任务API 这给了你更多的灵活性 以需要更多的手动管理为代价 在很多情况下 任务可能 不属于清晰的层次结构 最明显的是 如果你试图启动一个任务 来进行异步计算 你可能根本没有 来自非异步代码的父任务 或者 你想要的任务生命周期 可能不适合单个范围的范围 甚至是单一功能 例如 你可能想要启动一项任务 以响应将对象 置于活动状态的方法调用 然后取消其执行 以响应停用该对象的不同方法调用
在AppKit和UIKit中 实现委托对象时 会经常出现这种情况 UI工作必须在主线程上进行 正如Swift actor环节所讨论的那样 Swift通过声明属于主要actor的 UI类别来确保这一点
假设我们有一个集合视图 但我们还不能使用 集合视图数据源API 相反 我们想使用我们刚刚编写的 fetchThumbnails 函数从网络中获取缩略图 显示集合视图中的项目 然而 委托方法不是异步的 所以我们不能只是等待 对异步函数的调用 我们需要为此开始一项任务 但该任务实际上 是我们开始工作的延伸 响应委托行为 我们希望这个新任务仍然以 UI优先级在主要演员上运行 我们只是不想将任务的生命周期 绑定到这个单一代理方法的范围内 对于这样的情况 Swift允许我们构建一个 非结构化的任务 让我们将代码的异步部分 移动到一个闭包中 并传递该闭包来构造一个异步任务 现在这是运行时发生的事情 当我们到达创建任务的点时 Swift将安排它在与原始作用域 相同的演员上运行 在这种情况下 它是主要演员 同时 控制权立即返回给调用者 在有空档时 缩略图任务 将在主线程上运行 不会立即阻塞委托方法上的主线程 以这种方式构建任务 使我们介于结构化 和非结构化代码之间 直接构造的任务仍然会继承 其启动上下文的actor 如果有的话 并且还继承了源任务的优先级等特性 就像组任务或async-let一样 但是 新任务是无作用域的 它的生命周期不受其启动范围的限制 来源甚至不需要是异步的 我们可以在任何地方 创建一个无作用域的任务 为了获得所有这些灵活性 我们还必须手动管理事物 这种结构化并行性会自动处理 取消和错误不会自动传播 并且不会隐式等待任务的结果 除非我们采取明确的行动来这样做
所以我们启动了一个任务 来在显示集合视图项时获取缩略图 如果在缩略图准备好之前 将项目滚动到视图之外 我们也应该取消该任务 由于我们正在处理一个 无作用域的任务 因此取消不是自动的 现在让我们实现它 在我们构造好任务之后 让我们保存我们得到的值 我们可以在创建任务时将此值放入 以行索引为键的字典中 以便我们以后可以使用它 来取消该任务 我们还应该在任务完成后 将其从字典中删除 这样如果任务已经完成 我们就不会尝试取消它 请注意 我们可以在该异步任务 内部和外部访问相同的字典 没有得到编译程序标记的数据竞争 我们的委托类绑定到主要演员 而新任务继承了这一点 所以它们永远不会并行运行 我们可以安全地访问 主要actor绑定类的存储属性 在此任务中 无需担心数据竞争 同时 如果我们的委托稍后 被告知同一个表行 已从显示中删除 那么我们可以调用取消方法 值以取消任务 所以现在我们已经看到如何创建 独立于作用域运行的非结构化任务 同时仍然从该任务的 原始上下文中继承特征 但有时你不想从原始上下文中 继承任何内容 为了获得最大的灵活性 Swift提供了分离的任务 顾名思义 分离的任务独立于它们的上下文 它们仍然是非结构化的任务 它们的生命周期 不受其原始范围的约束 但是分离的任务也不会从 其原始范围中提取任何其他内容 默认情况下 它们不会 被限制在同一个演员上 也不必以相同的优先级运行 就像它们开始运转的地方一样 分离的任务独立运行 具有诸如优先级之类的通用默认值 但它们也可以使用可选参数启动 以控制新任务的执行方式和位置
假设我们从服务器获取缩略图后 我们想将它们写入本地磁盘缓存 所以如果我们稍后尝试获取它们 就不会再次访问网络 缓存不需要发生在主要演员身上 即使我们取消获取所有缩略图 缓存我们获取的任何缩略图 仍然很有帮助 因此 让我们使用分离的任务 来启动缓存 当我们分离一个任务时 我们在设置新任务的 执行方式方面也获得了更大的灵活性 缓存应该以不干扰主UI的 较低优先级发生 我们可以在分离这个新任务时 指定后台优先级
现在让我们提前计划一下 如果我们要在缩略图上 执行多个后台任务 我们将来应该怎么做? 我们可以分离更多后台任务 但我们也可以利用结构化并行性 在我们的独立任务中 我们可以将所有不同类型的 任务结合在一起 以发挥它们的优势
我们可以设置一个任务组 并生成每个后台作业 而不是为每个后台作业 分离一个独立的任务 作为子任务进入该组 这样做有很多好处 如果我们将来确实需要取消后台任务 使用任务组意味着 我们可以取消所有子任务 只需取消该顶级分离任务即可 然后取消会自动传播到子任务 我们不需要跟踪句柄数组 此外 子任务会自动继承 其父任务的优先级 为了将所有这些工作保持在后台 我们只需要将分离的任务设为后台 这将自动传播到它的所有子任务 所以我们不必担心忘记 传递设置背景优先级 和意外使UI工作饥饿 至此 我们已经看到了Swift中 所有主要的任务形式
Async-let允许 固定数量的子任务 生成为变量绑定 自动管理取消和错误传播 如果绑定超出范围 当我们需要动态数量的 仍受范围限制的子任务时 我们可以向上移动到任务组
如果我们需要中断一些范围不明确 但仍与其原始任务相关的工作 我们可以构建非结构化的任务 但我们需要手动管理它们 为了获得最大的灵活性 我们还有分离的任务 它们是手动管理的任务 没有从他们的起源继承任何东西 任务和结构化并行性只是 Swift支持的并行性功能 套件的一部分 请务必查看其他精彩讲座 以了解它如何与其他语言相适应 《在Swift中认识async/await》 为你提供有关异步函数的 更多详细信息 这为我们提供了结构化基础 用于编写并行性代码 actor会提供数据隔离以创建 不受数据竞争影响的并行性系统 请参阅《通过Swift actor 保护可变状态》讲座 以了解有关如何操作的更多信息
我们在任务组上 看到了“for await”循环 这些只是AsyncSequence的 一个例子 它提供了一个 用于处理异步数据流的标准接口 《认识AsyncSequence》座谈 深入探讨了 用于处理序列的可用API
任务与核心操作系统整合在一起 实现低开销和高扩展性 《Swift的并行性:幕后花絮》 提供了有关如何实现的更多技术细节
所有这些特性结合在一起 使得在Swift中编写并行性代码 变得简单而安全 充分利用你的设备编写代码 同时仍然专注于应用程序的有趣部分 不用多担心管理并行性任务的机制 或对多线程引起的潜在错误的烦恼 谢谢观看 希望你们都喜欢大会的其他活动 [轻快音乐]
-
-
1:57 - Asynchronous code with completion handlers is unstructured
func fetchThumbnails( for ids: [String], completion handler: @escaping ([String: UIImage]?, Error?) -> Void ) { guard let id = ids.first else { return handler([:], nil) } let request = thumbnailURLRequest(for: id) let dataTask = URLSession.shared.dataTask(with: request) { data, response, error in guard let response = response, let data = data else { return handler(nil, error) } // ... check response ... UIImage(data: data)?.prepareThumbnail(of: thumbSize) { image in guard let image = image else { return handler(nil, ThumbnailFailedError()) } fetchThumbnails(for: Array(ids.dropFirst())) { thumbnails, error in // ... add image to thumbnails ... } } } dataTask.resume() }
-
2:56 - Asynchronous code with async/await is structured
func fetchThumbnails(for ids: [String]) async throws -> [String: UIImage] { var thumbnails: [String: UIImage] = [:] for id in ids { let request = thumbnailURLRequest(for: id) let (data, response) = try await URLSession.shared.data(for: request) try validateResponse(response) guard let image = await UIImage(data: data)?.byPreparingThumbnail(ofSize: thumbSize) else { throw ThumbnailFailedError() } thumbnails[id] = image } return thumbnails }
-
7:59 - Structured concurrency with async-let
func fetchOneThumbnail(withID id: String) async throws -> UIImage { let imageReq = imageRequest(for: id), metadataReq = metadataRequest(for: id) async let (data, _) = URLSession.shared.data(for: imageReq) async let (metadata, _) = URLSession.shared.data(for: metadataReq) guard let size = parseSize(from: try await metadata), let image = try await UIImage(data: data)?.byPreparingThumbnail(ofSize: size) else { throw ThumbnailFailedError() } return image }
-
11:46 - Checking for cancellation by calling a method that throws
func fetchThumbnails(for ids: [String]) async throws -> [String: UIImage] { var thumbnails: [String: UIImage] = [:] for id in ids { try Task.checkCancellation() thumbnails[id] = try await fetchOneThumbnail(withID: id) } return thumbnails }
-
12:16 - Obtaining the cancellation status of the current task
func fetchThumbnails(for ids: [String]) async throws -> [String: UIImage] { var thumbnails: [String: UIImage] = [:] for id in ids { if Task.isCancelled { break } thumbnails[id] = try await fetchOneThumbnail(withID: id) } return thumbnails }
-
13:13 - Async-let is for concurrency with static width
func fetchThumbnails(for ids: [String]) async throws -> [String: UIImage] { var thumbnails: [String: UIImage] = [:] for id in ids { thumbnails[id] = try await fetchOneThumbnail(withID: id) } return thumbnails } func fetchOneThumbnail(withID id: String) async throws -> UIImage { // ... async let (data, _) = URLSession.shared.data(for: imageReq) async let (metadata, _) = URLSession.shared.data(for: metadataReq) // ... }
-
13:58 - A task group is for concurrency with dynamic width
func fetchThumbnails(for ids: [String]) async throws -> [String: UIImage] { var thumbnails: [String: UIImage] = [:] try await withThrowingTaskGroup(of: Void.self) { group in for id in ids { group.async { // Error: Mutation of captured var 'thumbnails' in concurrently executing code thumbnails[id] = try await fetchOneThumbnail(withID: id) } } } return thumbnails }
-
16:32 - Accessing the results of tasks within a group
func fetchThumbnails(for ids: [String]) async throws -> [String: UIImage] { var thumbnails: [String: UIImage] = [:] try await withThrowingTaskGroup(of: (String, UIImage).self) { group in for id in ids { group.async { return (id, try await fetchOneThumbnail(withID: id)) } } // Obtain results from the child tasks, sequentially, in order of completion. for try await (id, thumbnail) in group { thumbnails[id] = thumbnail } } return thumbnails }
-
20:39 - Creating an unstructured task
@MainActor class MyDelegate: UICollectionViewDelegate { func collectionView(_ view: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt item: IndexPath) { let ids = getThumbnailIDs(for: item) Task { let thumbnails = await fetchThumbnails(for: ids) display(thumbnails, in: cell) } } }
-
22:11 - Cancelling unstructured tasks
@MainActor class MyDelegate: UICollectionViewDelegate { var thumbnailTasks: [IndexPath: Task<Void, Never>] = [:] func collectionView(_ view: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt item: IndexPath) { let ids = getThumbnailIDs(for: item) thumbnailTasks[item] = Task { defer { thumbnailTasks[item] = nil } let thumbnails = await fetchThumbnails(for: ids) display(thumbnails, in: cell) } } func collectionView(_ view: UICollectionView, didEndDisplay cell: UICollectionViewCell, forItemAt item: IndexPath) { thumbnailTasks[item]?.cancel() } }
-
24:09 - Detaching a task
@MainActor class MyDelegate: UICollectionViewDelegate { var thumbnailTasks: [IndexPath: Task<Void, Never>] = [:] func collectionView(_ view: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt item: IndexPath) { let ids = getThumbnailIDs(for: item) thumbnailTasks[item] = Task { defer { thumbnailTasks[item] = nil } let thumbnails = await fetchThumbnails(for: ids) Task.detached(priority: .background) { writeToLocalCache(thumbnails) } display(thumbnails, in: cell) } } }
-
24:57 - Creating a task group inside a detached task
@MainActor class MyDelegate: UICollectionViewDelegate { var thumbnailTasks: [IndexPath: Task<Void, Never>] = [:] func collectionView(_ view: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt item: IndexPath) { let ids = getThumbnailIDs(for: item) thumbnailTasks[item] = Task { defer { thumbnailTasks[item] = nil } let thumbnails = await fetchThumbnails(for: ids) Task.detached(priority: .background) { withTaskGroup(of: Void.self) { g in g.async { writeToLocalCache(thumbnails) } g.async { log(thumbnails) } g.async { ... } } } display(thumbnails, in: cell) } } }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。