-
前言
Swift 提供了异步和并行代码的结构化支持。异步代码可以暂停并稍后恢复,使程序在执行长时间任务时仍能处理短期操作,如更新 UI。并行代码则允许多段代码同时运行,比如在四核处理器上同时执行四个任务。
如果你以前编写过并发代码,可能会习惯于使用线程。Swift 的并发模型基于线程,但你不需要直接与线程交互。在 Swift 中,异步函数可以释放它正在运行的线程,这样另一个异步函数就可以在该线程上运行,而第一个函数则处于阻塞状态。当异步函数恢复执行时,Swift 不保证在哪个线程运行。
-
异步函数
异步函数是一种特殊的函数,它在执行过程中可以被挂起(不会阻塞当前线程)。它可以在等待某些事情发生时暂停。在异步函数体内,你需要标记可能发生暂停的地方。
func saveImageFromNetwork() async throws {
let urlString = "https://cdn.arstechnica.net/wp-content/uploads/2018/06/macOS-Mojave-Dynamic-Wallpaper-transition.jpg"
guard let url = URL(string: urlString) else {
return
}
let data = try await fetchImageFromNetwork(url: url)
guard let fileURL = getPicturesFilePath(fileName: "download.jpg") else {
return
}
guard !checkIfTheFileExist(filePath: fileURL.path) else {
return
}
try await saveImageToDisk(fileURL: fileURL, data: data)
}
考察例程saveImageFromNetwork() async throws, 这是一个可以抛出异常的异步函数。
这个异步函数可能得执行过程如下
-
首先这段代码会从开始执行到第一个
await。 这里会调用另外一个异步函数fetchImageFromNetwork。此时当前异步函数会中断,等待fetchImageFromNetwork返回。 -
当前执行点被挂起的时候,其他的并发代码会运行。
-
当
fetchImageFromNetwork返回时,代码会从中断点处继续顺序往下执行。它将从网络获取的ImageData 赋值给局部变量data -
getPicturesFilePath和 checkIfTheFileExist 是同步函数,这里没有中断点。 -
下一个
await标记了对downloadPhoto(named:)函数的调用。此代码再次暂停执行,直到该函数返回,从而给其他并发代码一个运行的机会。
代码中标记为 await 的可能挂起点表示当前代码可能在等待异步函数返回时暂停执行。这时Swift 会暂停当前线程上的代码执行,并在该线程上运行其他代码。这又被称为让出线程。使用Task.yield()可以主动让出线程。
注意: 可以调用异步函数的地方
- 异步函数可以调用异步函数
- 非结构化任务
- 标记为
@main的结构体、类或枚举的静态main()方法中的代码。
-
异步序列
Swift 的异步序列(Asynchronous Sequences)是 Swift Concurrency 模型的一部分,用于处理异步数据流。异步序列允许我们逐步接收异步数据,类似于同步序列,但数据的生成是异步的。
struct Counter: AsyncSequence {
typealias Element = Int
let howHigh: Int
struct AsyncIterator: AsyncIteratorProtocol {
let howHigh: Int
var current = 1
mutating func next() async throws -> Int? {
guard current <= howHigh else {
return nil
}
if Task.isCancelled {
throw CancellationError()
}
try await Task.sleep(nanoseconds: 1_000_000_000)
let result = current
current += 1
return result
}
}
func makeAsyncIterator() -> AsyncIterator {
return AsyncIterator(howHigh: howHigh)
}
}
//调用
let counter = Counter(howHigh: 10)
Task {
for try await value in counter {
print(value)
}
}
-
并发调用异步函数
await: 当后续代码依赖于异步函数的结果时,可以使用 await 调用异步函数。这种方式创建的工作是顺序执行的。
async let: 当你不需要立即获取结果时,可以使用 async-let 调用异步函数。这种方式创建的工作可以并行进行。
-
串行版本
let data1 = await fetchData(from: url1)
let data2 = await fetchData(from: url2)
let data3 = await fetchData(from: url3)
let dataArray = [data1, data2, data3]
-
并发版本
async let data1 = fetchData(from: url1)
async let data2 = fetchData(from: url2)
async let data3 = fetchData(from: url3)
// 这里需要等待这三个异步任务的结果
let dataArray = await [data1, data2, data3]
-
结构化并发
Task: 任务是程序中可以异步运行的工作单元。所有的异步代码都是作为某个任务的一部分运行的。一个任务本身一次只做一件事,但当你创建多个任务时,Swift 可以调度它们同时运行。
除了使用async let隐式使用并发,你还可以显示创建TaskGroup并添加子任务到这个组,它赋予你更多的优先级和取消控制,并让你创建动态数量的任务。
给定任务组中的每个任务都有相同的父任务,每个任务都可以有子任务。由于任务和任务组之间的明确关系,这种方法被称为结构化并发。
改造上节例程
let dataArray = await withTaskGroup(of: String.self) { group in
//添加父任务
let urls = [url1, url2, url3]
for url in urls {
//添加子任务
group.addTask {
return await self.fetchData(from: url)
}
}
var results: [String] = []
for await data in group {
results.append(data)
}
return results
}
以上代码创建了一个TaskGroup,并给这个组添加了三个获取数据的子任务,这三个子任务会并行执行,然后从group(异步序列)收集数据存入数组返回。
-
非结构化并发
非结构化任务没有父任务。我们可以使用Task.detached和Task.init创建非结构化任务。你可以自己多次调用非结构化任务来实现非结构化并发。
创建一个非结构化任务:
//创建任务
let data = Task {
await fetchData(from:"www.apple.com")
}
//等待任务执行完毕
let result = await data.value
-
取消任务
Task和TaskGroup支持用户取消,这使得用户不用等所有任务完成,任务需要做检查任务取消状态并且暂停当前任务。我们有两个方法检查取消状态,在任务中间调用Task.checkCancellation()和Task.isCancelled。如果任务被取消,使用Task.checkCancellation()会使任务抛出异常停止任务,并且能够停止所有任务。你如果想在取消的时候做一些清理工作使用Task.isCancelled。
func cancelTask() async {
let task = Task { ()-> String? in
let data = await fetchData(from: url)
if Task.isCancelled {//如果任务被取消返回空
return nil
}
return data
}
//模拟取消
task.cancel()
let data = await task.value
print(data ?? "empty")
}
let dataArray = await withTaskGroup(of: Optional<String>.self) { group in
for url in [url1,url2,url3] {
//如果任务没有被取消,添加成功
let added = group.addTaskUnlessCancelled {
guard !Task.isCancelled else {//任务被取消,返回空值
return nil
}
return await self.fetchData(from: url)
}
guard added else { break }
}
var results: [String] = []
for await data in group {
if let data {// 过滤空值
results.append(data)
}
}
return results
}
-
结语
Swift 提供了强大的异步和并行编程模型,简化了在现代应用程序中编写高效并发代码的过程。通过异步函数和并行执行,程序可以在处理长时间任务的同时保持对用户界面的响应。Swift 的并发模型使得我们可以专注于任务逻辑,而无需直接处理线程的复杂性。
通过这些特性,我们可以更轻松地编写高性能、高可靠性的应用程序,确保在处理繁重任务时,仍能保持流畅的用户体验。随着 Swift 并发特性的不断完善和发展,我们可以期待在未来的项目中实现更高效、更安全的并发编程。
希望本文对您理解和应用 Swift 的并发模型有所帮助。有疑问请不吝笔墨。