Share E-Book

Swift 异步与并发编程 (王 巍) (Z-Library)

Author 王 巍

Technology
Language English

Async Swift

Format PDF
Size 8.3 MB
307
Views
4
Downloads
0.00
Total Donations
(First 20 pages)

Registered users can read the full content for free

Register as a Gaohf Library member to read the complete e-book online for free and enjoy a better reading experience.

Page 1
(This page has no text content)
Page 2
Swift 异步与并发编程 王 巍 ObjC 中国 © 2021~ ObjC 中国
Page 3
Swift 异步与并发编程 1. 简介 1. ⽬标读者 2. 章节结构 3. 准备⼯作 2. Swift 并发初步 1. ⼀些基本概念 2. 异步函数 3. 结构化并发 4. actor 模型和数据隔离 5. ⼩结 3. 创建异步函数 1. 异步函数的动机 2. 转换函数签名 3. 使⽤续体改写函数 4. Objective-C ⾃动转换 5. Async getter 6. ⼩结 4. 异步序列 1. 同步序列和异步序列 2. 异步迭代器 3. 操作异步序列 4. AsyncStream 5. 异步序列和响应式编程 6. ⼩结
Page 4
5. 使⽤异步函数 1. ⽹络请求中的异步函数 2. Notification 3. 异步函数的运⾏环境 4. ⼩结 6. 结构化并发 1. 什么是结构化 2. 基于 Task 的结构化并发模型 3. ⾮结构化任务 4. ⼩结 7. 协作式任务取消 1. 任务取消到底做了什么 2. 处理任务取消 3. 取消的清理⼯作 4. 隐式等待和任务暂停 5. ⼩结 8. actor 模型和数据隔离 1. 共享内存模型的困境 2. Actor 隔离 3. Actor 协议 4. ⼩结 9. 全局 actor,可重⼊和 Sendable 1. 全局 actor 2. 可重⼊ 3. Sendable 4. ⼩结 10. 并发线程模型 1. 协同式线程池 2. 执⾏器
Page 5
3. 任务本地值和任务追踪 4. ⼩结 11. 总结和展望 1. 总结 2. 更新履历
Page 6
简介 在 Swift “七年之痒” 的 2021 年,“千呼万唤始出来”的 Swift 并发编程 犹如⼀剂强⼼针,出现在了⼤家⾯前。当⼴⼤ Swift 开发者们还沉浸 在终于得到了 async 和 await 的欢喜之时,我们不禁要想,对⽐起⼀ 些同级别的语⾔,这⼀切似乎有些姗姗来迟:并发和异步编程的前辈 语⾔ C# 早在 2012 年就加⼊了异步⽅法和任务 API;隔壁同为主打客 ⼾端开发起家的 Kotin 在 2016 年的 1.1 版中引⼊了协程 (coroutines) 预 览;与 Swift 社区有着不解之缘的 Rust 开发者们,也从 2019 年开始就 可以⾃由地徜徉在异步编程的世界中了。 为什么 Swift 中语⾔及标准库级别的异步和并发编程开发进度相对缓 慢,以⾄于那么多少年都“等⽩了头”?Swift 的异步编程模型要如何使 ⽤,是不是⽆脑跟着编译器提⽰写代码就万事⼤吉?Swift 并发编程和 其他语⾔中类似的概念有什么异同,学习这些概念能对其他语⾔的使 ⽤有所帮助吗?我们要如何更新⾃⼰的理解和认知,来利⽤这些新特 性完善我们的代码和项⽬,并进⼀步精进⾃⼰的技艺呢?在这本书 中,我想要带着这些问题,对 Swift 的异步和并发编程做⼀些研究, 和⼤家⼀同学习和探索。 ⽬标读者 这本书⾯向的是已经⼊⻔ Swift 并⾄少可以理解 Swift 语法、熟悉标准 库使⽤的开发者。我们会从像是⽅法调⽤或线程调度等⼀些基本概念 开始,逐步揭⽰出 Swift 异步和并发编程世界的特性,同时讲解⼤部 分语⾔和标准库中的有关概念。但我们并不会逐⾏解释程序 (特别是 那些同步世界中的代码) 的语法和运⾏机制。如果您想要从零开始学 习 Swift,或者接触 Swift 的时间还很短,那么在阅读时有可能会有⼀ 些难以理解的地⽅。如果遇到这种情况,建议您可以先阅读 Apple 官
Page 7
⽅的 Swift 教程中的相关内容 (您也可以在这⾥找到这些⽂档的中⽂版 本)。 特别地,您可能会需要有⼀定的 Swift 实际开发经验,才能更透彻地 理解 Swift 并发相关的内容,⾄少您应该使⽤过像是回调 (callback), 代理 (delegate) 等模式处理基本的异步操作。如果您⽤ Swift 开发过实 际项⽬的话,这个要求肯定不成问题:相关的操作遍布在 Swift 标准 库和⽇常的开发框架 (Foundation, UIKit 等) 的各个地⽅。如果您 (不幸 地) 主要还在使⽤ Objective-C 进⾏⽇常开发,您也可以通过本书理解 Swift 在并发编程⽅⾯所具有的巨⼤优势,这些知识在今后您进⾏迁移 时必将成为助⼒。 作为本书的读者,可能您对 Swift 相关的其他话题也会有兴趣,欢迎 查看我们的其他书籍。您可以在 ObjC 中国的⽹站找到其他所有书籍 的信息,它们包含了 Swift 开发的各个⽅⾯,相信您⼀定能找到⾃⼰ 感兴趣的话题,并藉此构建出更加完整的 Swift 知识体系。 章节结构 本书分为五个部分,它们包括了⼀个综述部分,三个对 Swift 并发中 主要模块进⾏详细解释的部分,以及最后对并发底层模型和调度⽅⾯ 说明的部分。具体来说: ⾸先是对 Swift 异步和并发编程模型的综述,在这个部分中,我 们希望阐明要解决的问题,并对 Swift 异步和并发的整套⽅案在 宏观上进⾏⼀个概览。我们⾸先要釐清⼀些基本的概念,虽然 Apple 使⽤ Swift 并发 (Swift Concurrency) 这个名词来描述 Swift 5.5 中加⼊的这些特性,但并发这个词在计算机科学中具有更⼴阔 的意涵。我们会通过⼀个具体的例⼦,对相关概念进⾏解释,并 基于这个例⼦讲述最基本的 Swift 并发编程⽅式。如果你只需要 不求甚解地“按照编译器提⽰”去处理简单的并发⼯作,那阅读这 个部分⼤概就可以覆盖你的⽇常需求了。
Page 8
对于希望“知其然,更知其所以然”的读者,第⼆个部分会开始对 Swift 并发编程进⾏深⼊探索,包括研究它的设计思想和部分实现 机理。Swift 并发⼤致由三块主要内容构成:异步函数、结构化并 发、以及 actor 模型。另外还有像是异步序列 (async sequence)、 任务本地值 (task local value) 等⼩⼀些的概念。这些内容环环相 扣,⼀同构建了 Swift 的并发模型。在这部分中,我们⼒求对这 些概念进⾏详细阐明,并探求⼀些更深⼊的话题及细节。 最后是对 Swift 并发底层机制的“刨根问底”,包括协作式线程池 的调度⽅式、异步函数的线程模型和执⾏器相关的话题。我们不 会去纠结源码级别的实现细节,但是会揭⽰ Swift 并发的⼀些底 层思想,以及探索怎样才能让 Swift 并发机制和现有的代码协同 合作并保证它的性能优势。 章节之间是有顺序关系的,笔者推荐按照书籍顺序去阅读每个章节, ⽽不是跳过其中某些内容。Swift 异步和并发编程的模型具有很⾼的统 合性,它们环环相扣:语法的设计和标准库中 API 的设计相辅相成, 合⼒解决了⼀系列在 Apple 平台编程时原本⼗分困难的问题,同时对 外提供了相对优雅的 API 设计和强有⼒的编译期间保证。按照顺序阅 读的话,可以有利于层层递进,逐步了解 Swift 异步和并发模型的设 计思路。 准备⼯作 Swift 在 5.5 版本中加⼊了异步⽅法的语⾔特性和⼀系列并发相关的 API,所以想要实践本书内容,你⾄少需要搭载 Swift 5.5 的开发环 境。如果你使⽤的是 macOS,最简单的⽅式就是登⼊ Apple 开发者⽹ 站,并下载 Xcode 13 或者后续版本。如果你使⽤ Linux 系统,也可以 遵循 Swift 官⽅⽹站的说明,下载并安装对应版本的 Swift。 当你准备好后,可以通过运⾏ swift 命令来确认你的 Swift ⼯具链版本 号,⼀切正常的话,应该可以看到类似的输出。请确认 Swift 版本号 ⼤于等于 5.5:
Page 9
$ swift --version Apple Swift version 5.5 (swiftlang-xxxx clang-yyyy) 我们已经准备好开始进⼊到 Swift 并发的世界中了!
Page 10
Swift 并发初步 虽然可能你已经跃跃欲试,想要创建第⼀个 Swift 的并发程序,但是 “名不正则⾔不顺”。在实际进⼊代码之前,作为全书开头,我还是想 先对⼏个重要的相关概念进⾏说明。这样在今后本书中,当我们提起 Swift 异步和并发时,对具体它指代了什么内容,能够取得统⼀的认 识。本章后半部分,我们会实际着⼿写⼀些 Swift 并发代码,来描述整 套体系的基本构成和⼯作流程。 ⼀些基本概念 同步和异步 在我们说到线程的执⾏⽅式时,同步 (synchronous) 和异步 (asynchronous) 是这个话题中最基本的⼀组概念。同步操作意味着在操 作完成之前,运⾏这个操作的线程都将被占⽤,直到函数最终被抛出 或者返回。Swift 5.5 之前,所有的函数都是同步函数,我们简单地使 ⽤ func 关键字来声明这样⼀个同步函数: var results: [String] = [] func addAppending(_ value: String, to string: String) { results.append(value.appending(string)) } addAppending 是⼀个同步函数,在它返回之前,运⾏它的线程将⽆ 法执⾏其他操作,或者说它不能被⽤来运⾏其他函数,必须等待当前 函数执⾏完成后这个线程才能做其他事情。
Page 11
在 iOS 开发中,我们使⽤的 UI 开发框架,也就是 UIKit 或者 SwiftUI,不是线程安全的:对⽤⼾输⼊的处理和 UI 的绘制,必须在与 主线程绑定的 main runloop 中进⾏。假设我们希望⽤⼾界⾯以每秒 60 帧的速率运⾏,那么主线程中每两次绘制之间,所能允许的处理时间 最多只有 16 毫秒 (1 / 60s)。当主线程中要同步处理的其他操作耗时很 少时 (⽐如我们的 addAppending,可能耗时只有⼏⼗纳秒),这不会造 成什么问题。但是,如果这个同步操作耗时过⻓的话,主线程将被阻 塞。它不能接受⽤⼾输⼊,也⽆法向 GPU 提交请求去绘制新的 UI,这 将导致⽤⼾界⾯掉帧甚⾄卡死。这种“⻓耗时”的操作,其实是很常⻅ 的:⽐如从⽹络请求中获取数据,从磁盘加载⼀个⼤⽂件,或者进⾏ 某些⾮常复杂的加解密运算等。 下⾯的 loadSignature 从某个⽹络 URL 读取字符串:如果这个操作发 ⽣在主线程,且耗时超过 16ms (这是很可能发⽣的,因为通过握⼿协 议建⽴⽹络连接,以及接收数据,都是⼀系列复杂操作),那么主线程 将⽆法处理其他任何操作,UI 将不会刷新。 // 从⽹络读取⼀个字符串 func loadSignature() throws -> String? { // someURL 是远程 URL,⽐如 https://example.com let data = try Data(contentsOf: someURL) return String(data: data, encoding: .utf8) } loadSignature 最终的耗时超过 16 ms,对 UI 的刷新或操作的处理不 得不被延后。在⽤⼾观感上,将表现为掉帧或者整个界⾯卡住。这是 客⼾端开发中绝对需要避免的问题之⼀。 Swift 5.5 之前,要解决这个问题,最常⻅的做法是将耗时的同步操作 转换为异步操作:把实际⻓时间执⾏的任务放到另外的线程 (或者叫做
Page 12
后台线程) 运⾏,然后在操作结束时提供运⾏在主线程的回调,以供 UI 操作之⽤: func loadSignature( _ completion: @escaping (String?, Error?) -> Void ) { DispatchQueue.global().async { do { let d = try Data(contentsOf: someURL) DispatchQueue.main.async { completion(String(data: d, encoding: .utf8), nil) } } catch { DispatchQueue.main.async { completion(nil, error) } } } } DispatchQueue.global 负责将任务添加到全局后台派发队列。在底 层,GCD 库 (Grand Central Dispatch) 会进⾏线程调度,为实际耗时繁 重的 Data.init(contentsOf:) 分配合适的线程。耗时任务在主线程外进 ⾏处理,完成后再由 DispatchQueue.main 派发回主线程,并按照结 果调⽤ completion 回调⽅法。这样⼀来,主线程不再承担耗时任务, UI 刷新和⽤⼾事件处理可以得到保障。 异步操作虽然可以避免卡顿,但是使⽤起来存在不少问题,最主要包 括:
Page 13
错误处理隐藏在回调函数的参数中,⽆法⽤ throw 的⽅式明确地 告知并强制调⽤侧去进⾏错误处理。 对回调函数的调⽤没有编译器保证,开发者可能会忘记调⽤ completion,或者多次调⽤ completion。 通过 DispatchQueue 进⾏线程调度很快会使代码复杂化。特别是 如果线程调度的操作被隐藏在被调⽤的⽅法中的时候,不查看源 码的话,在 (调⽤侧的) 回调函数中,⼏乎⽆法确定代码当前运⾏ 的线程状态。 对于正在执⾏的任务,没有很好的取消机制。 除此之外,还有其他⼀些没有列举的问题。它们都可能成为我们程序 中潜在 bug 的温床,在之后关于异步函数的章节⾥,我们会再回顾这 个例⼦,并仔细探讨这些问题的细节。 需要进⾏说明的是,虽然我们将运⾏在后台线程加载数据的⾏为称为 异步操作,但是接受回调函数作为参数的 loadSignature(_:) ⽅法,其 本⾝依然是⼀个同步函数。这个⽅法在返回前仍旧会占据主线程,只 不过它现在的执⾏时间⾮常短,UI 相关的操作不再受影响。 Swift 5.5 之前,Swift 语⾔中并没有真正异步函数的概念,我们稍后会 看到使⽤ async 修饰的异步函数是如何简化上⾯的代码的。 串⾏和并⾏ 另外⼀组重要的概念是串⾏和并⾏。对于通过同步⽅法执⾏的同步操 作来说,这些操作⼀定是以串⾏⽅式在同⼀线程中发⽣的。“做完⼀件 事,然后再进⾏下⼀件事”,是最常⻅的、也是我们⼈类最容易理解的 代码执⾏⽅式: if let signature = try loadSignature() { addAppending(signature, to: "some data") } print(results)
Page 14
loadSignature,addAppending 和 print 被顺次调⽤,它们在同⼀线 程中按严格的先后顺序发⽣。这种执⾏⽅式,我们将它称为串⾏ (serial)。 同步⽅法执⾏的同步操作,是串⾏的充分但⾮必要条件。异步操作也 可能会以串⾏⽅式执⾏。假设除了 loadSignature(_:) 以外,我们还有 ⼀个从数据库⾥读取⼀系列数据的函数,它使⽤类似的⽅法,把具体 ⼯作放到其他线程异步执⾏: func loadFromDatabase( _ completion: @escaping ([String]?, Error?) -> Void ) { // ... } 如果我们先从数据库中读取数据,在完成后再使⽤ loadSignature 从 ⽹络获取签名,最后将签名附加到每⼀条数据库中取出的字符串上, 可以这么写: loadFromDatabase { (strings, error) in if let strings = strings { loadSignature { signature, error in if let signature = signature { strings.forEach { addAppending(signature, to: $0) } } else { print("Error") } } } else { print("Error.") } } 虽然这些操作是异步的,但是它们 (从数据库读取 [String],从⽹络下 载签名,最后将签名添加到每条数据中) 依然是串⾏的,加载签名必定
Page 15
发⽣在读取数据库完成之后,⽽最后的 addAppending 也必然发⽣在 loadSignature 之后: 虽然图中把 loadFromDatabase 和 loadSignature 画在了同⼀个 线程⾥,但事实上它们有可能是在不同线程执⾏的。不过在上⾯ 代码的情况下,它们的先后次序依然是严格不变的。 事实上,虽然最后的 addAppending 任务同时需要原始数据和签名才 能进⾏,但 loadFromDatabase 和 loadSignature 之间其实并没有依 赖关系。如果它们能够⼀起执⾏的话,我们的程序有很⼤机率能变得 更快。这时候,我们会需要更多的线程,来同时执⾏两个操作: // loadFromDatabase { (strings, error) in // ... // loadSignature { signature, error in { // ... // 可以将串⾏调⽤替换为: loadFromDatabase { (strings, error) in //... } loadSignature { signature, error in //... } 为了确保在 addAppending 执⾏时,从数据库加载的内容和从⽹ 络下载的签名都已经准备好,我们需要某种⼿段来确保这些数据 的可⽤性。在 GCD 中,通常可以使⽤ DispatchGroup 或者 DispatchSemaphore 来实现这⼀点。但是我们并不是⼀本探讨 GCD 的书籍,所以这部分内容就略过了。
Page 16
两个 load ⽅法同时开始⼯作,理论上资源充⾜的话 (⾜够的 CPU,⽹ 络带宽等),现在它们所消耗的时间会⼩于串⾏时的两者之和: 这时候,loadFromDatabase 和 loadSignature 这两个异步操作,在 不同的线程中同时执⾏。对于这种拥有多套资源同时执⾏的⽅式,我 们就将它称为并⾏ (parallel)。 Swift 并发是什么 在有了这些基本概念后,最后可以谈谈关于并发 (concurrency) 这个名 词了。在计算机科学中,并发指的是多个计算同时执⾏的特性。并发 计算中涉及的同时执⾏,主要是若⼲个操作的开始和结束时间之间存 在重叠。它并不关⼼具体的执⾏⽅式:我们可以把同⼀个线程中的多 个操作交替运⾏ (这需要这类操作能够暂时被置于暂停状态) 叫做并 发,这⼏个操作将会是分时运⾏的;我们也可以把在不同处理器核⼼ 中运⾏的任务叫做并发,此时这些任务必定是并⾏的。 ⽽当 Apple 在定义“Swift 并发”是什么的时候,和上⾯这个经典的计算 机科学中的定义实质上没有太多不同。Swift 官⽅⽂档给出了这样的解 释: Swift 提供内建的⽀持,让开发者能以结构化的⽅式书写异步和并 ⾏的代码,… 并发这个术语,指的是异步和并⾏这⼀常⻅组合。
Page 17
所以在提到 Swift 并发时,它指的就是异步和并⾏代码的组合。这在语 义上,其实是传统并发的⼀个⼦集:它限制了实现并发的⼿段就是异 步代码,这个限定降低了我们理解并发的难度。在本书中,如果没有 特别说明,我们在提到 Swift 并发时,指的都是“异步和并⾏代码的组 合”这个简化版的意义,或者专指 Swift 5.5 中引⼊的这⼀套处理并发的 语法和框架。 除了定义⽅式稍有不同之外,Swift 并发和其他编程语⾔在处理同样问 题时所⾯临的挑战⼏乎⼀样。从戴克斯特拉 (Edsger W. Dijkstra) 提出信 号量 (semaphore) 的概念起,到东尼·霍尔爵⼠ (Tony Hoare) 使⽤ CSP 描述和尝试解决哲学家就餐问题,再到 actor 模型或者通道模型 (channel model) 的提出,并发编程最⼤的困难,以及这些⼯具所要解决 的问题⼤致上只有两个: 1. 如何确保不同运算运⾏步骤之间的交互或通信可以按照正确的顺 序执⾏ 2. 如何确保运算资源在不同运算之间被安全地共享、访问和传递 第⼀个问题负责并发的逻辑正确,第⼆个问题负责并发的内存安全。 在以前,开发者在使⽤ GCD 编写并发代码时往往需要很多经验,否则 难以正确处理上述问题。Swift 5.5 设计了异步函数的书写⽅法,在此 基础上,利⽤结构化并发确保运算步骤的交互和通信正确,利⽤ actor 模型确保共享的计算资源能在隔离的情况下被正确访问和操作。它们 组合在⼀起,提供了⼀系列⼯具让开发者能简单地编写出稳定⾼效的 并发代码。我们接下来,会浅显地对这⼏部分内容进⾏瞥视,并在后 ⾯对各个话题展开探究。 戴克斯特拉还发表了著名的《GOTO 语句有害论》(Go To Statement Considered Harmful),并和霍尔爵⼠⼀同推动了结构化编 程的发展。霍尔爵⼠在稍后也提出了对 null 的反对,最终促成了 现代语⾔中普遍采⽤的 Optional (或者叫别的名称,⽐如 Maybe 或 null safety 等) 设计。如果没有他们,也许我们今天在编写代码 时还在处理⽆尽的 goto 和 null 检查,会要⾟苦很多。
Page 18
异步函数 为了更容易和优雅地解决上⾯两个问题,Swift 需要在语⾔层⾯引⼊新 的⼯具:第⼀步就是添加异步函数的概念。在函数声明的返回箭头前 ⾯,加上 async 关键字,就可以把⼀个函数声明为异步函数: func loadSignature() async throws -> String { fatalError("暂未实现") } 异步函数的 async 关键字会帮助编译器确保两件事情: 1. 它允许我们在函数体内部使⽤ await 关键字; 2. 它要求其他⼈在调⽤这个函数时,使⽤ await 关键字。 这和与它处于类似位置的 throws 关键字有点相似。在使⽤ throws 时,它允许我们在函数内部使⽤ throw 抛出错误,并要求调⽤者使⽤ try 来处理可能的抛出。async 也扮演了这样⼀个⻆⾊,它要求在特定 情况下对当前函数进⾏标记,这是对于开发者的⼀种明确的提⽰,表 明这个函数有⼀些特别的性质:try/throw 代表了函数可以被抛出,⽽ await 则代表了函数在此处可能会放弃当前线程,它是程序的潜在暂停 点。 放弃线程的能⼒,意味着异步⽅法可以被“暂停”,这个线程可以被⽤ 来执⾏其他代码。如果这个线程是主线程的话,那么界⾯将不会卡 顿。被 await 的语句将被底层机制分配到其他合适的线程,在执⾏完 成后,之前的“暂停”将结束,异步⽅法从刚才的 await 语句后开始, 继续向下执⾏。 关于异步函数的设计和更多深⼊内容,我们会在随后的相关章节展 开。在这⾥,我们先来看看⼀个简单的异步函数的使⽤。Foundation 框 架中已经为我们提供了很多异步函数,⽐如使⽤ URLSession 从某个 URL 加载数据,现在也有异步版本了。在由 async 标记的异步函数 中,我们可以调⽤其他异步函数:
Page 19
func loadSignature() async throws -> String? { let (data, _) = try await URLSession.shared.data(from: someURL) return String(data: data, encoding: .utf8) } 这些 Foundation,或者 AppKit 或 UIKit 中的异步函数,有⼀部分 是重写和新添加的,但更多的情况是由相应的 Objective-C 接⼝转 换⽽来。满⾜⼀定条件的 Objective-C 函数,可以直接转换为 Swift 的异步函数,⾮常⽅便。在后⼀章我们也会具体谈到。 如果我们把 loadFromDatabase 也写成异步函数的形式。那么,在上 ⾯串⾏部分,原本的嵌套式的异步操作代码: loadFromDatabase { (strings, error) in if let strings = strings { loadSignature { signature, error in if let signature = signature { strings.forEach { addAppending(signature, to: $0) } } else { print("Error") } } } else { print("Error.") } } 就可以⾮常简单地写成这样的形式: let strings = try await loadFromDatabase() if let signature = try await loadSignature() { strings.forEach { addAppending(signature, to: $0) } } else { throw NoSignatureError() } 不⽤多说,单从代码⾏数就可以⼀眼看清优劣了。异步函数极⼤简化 了异步操作的写法,它避免了内嵌的回调,将异步操作按照顺序写成 了类似“同步执⾏”的⽅法。另外,这种写法允许我们使⽤ try/throw 的
Page 20
组合对错误进⾏处理,编译器会对所有的返回路径给出保证,⽽不必 像回调那样时刻检查是不是所有的路径都进⾏了处理。 结构化并发 对于同步函数来说,线程决定了它的执⾏环境。⽽对于异步函数,则 由任务 (Task) 决定执⾏环境。Swift 提供了⼀系列 Task 相关 API 来让 开发者创建、组织、检查和取消任务。这些 API 围绕着 Task 这⼀核⼼ 类型,为每⼀组并发任务构建出⼀棵结构化的任务树: ⼀个任务具有它⾃⼰的优先级和取消标识,它可以拥有若⼲个⼦ 任务并在其中执⾏异步函数。 当⼀个⽗任务被取消时,这个⽗任务的取消标识将被设置,并向 下传递到所有的⼦任务中去。 ⽆论是正常完成还是抛出错误,⼦任务会将结果向上报告给⽗任 务,在所有⼦任务完成之前 (不论是正常结束还是抛出),⽗任务 是不会完成的。 这些特性看上去和 Operation 类 有⼀些相似,不过 Task 直接利⽤异步 函数的语法,可以⽤更简洁的⽅式进⾏表达。⽽ Operation 则需要依 靠⼦类或者闭包。 在调⽤异步函数时,需要在它前⾯添加 await 关键字;⽽另⼀⽅⾯, 只有在异步函数中,我们才能使⽤ await 关键字。那么问题在于,第 ⼀个异步函数执⾏的上下⽂,或者说任务树的根节点,是怎么来的? 简单地使⽤ Task.init 就可以让我们获取⼀个任务执⾏的上下⽂环境, 它接受⼀个 async 标记的闭包: struct Task<Success, Failure> where Failure : Error { init( priority: TaskPriority? = nil, operation: @escaping @Sendable () async throws -> Success
The above is a preview of the first 20 pages. Register to read the complete e-book.

Recommended for You

Loading recommended books...
Failed to load, please try again later

Tip the Site

Scan the WeChat Pay or Alipay code to tip. No login required.

WeChat Pay
Alipay
Back to List