iOS Swift 并发与任务取消

举报
yd_290101916 发表于 2026/10/01 10:46:04 2026/10/01
【摘要】 iOS Swift 并发与任务取消摘要:Swift 的 async/await、Task 和 Actor 让异步代码更容易阅读,也提供了结构化并发的生命周期约束。本文从异步函数、并发子任务、主线程隔离到取消处理,说明如何写出可控的 iOS 并发代码。 一、异步函数表达等待点async 标记一个可能挂起的函数,await 标记调用方可能暂停的位置。挂起不会阻塞当前线程:struct Arti...

iOS Swift 并发与任务取消

摘要:Swift 的 async/await、Task 和 Actor 让异步代码更容易阅读,也提供了结构化并发的生命周期约束。本文从异步函数、并发子任务、主线程隔离到取消处理,说明如何写出可控的 iOS 并发代码。

一、异步函数表达等待点

async 标记一个可能挂起的函数,await 标记调用方可能暂停的位置。挂起不会阻塞当前线程:

struct Article: Decodable {
    let id: Int
    let title: String
}

func loadArticles(using service: ArticleService) async throws -> [Article] {
    try await service.fetchArticles()
}

调用异步函数的上下文也需要支持 await。错误可通过 throws 向上传递,调用方使用 do/catch 决定如何呈现。

二、并发执行独立工作

若两个请求互不依赖,可使用任务组并发启动并聚合结果:

func loadDashboard() async throws -> Dashboard {
    async let profile = service.fetchProfile()
    async let summary = service.fetchSummary()
    return try await Dashboard(profile: profile, summary: summary)
}

async let 创建结构化子任务,父任务离开作用域时会等待子任务完成,并遵循取消传播。不要为了并发而并发:若任务依赖前一步结果,仍需按顺序执行。

对动态数量的同类工作可使用 withTaskGroup 或 withThrowingTaskGroup,并控制并发数,避免一次发起过多网络请求。

三、把 UI 更新隔离到主执行器

SwiftUI 模型或 UIKit 状态通常应在主执行器更新。使用 @MainActor 约束相关类型或方法:

@MainActor
final class ArticleModel: ObservableObject {
    @Published private(set) var articles: [Article] = []
    @Published private(set) var message: String?

    func reload() async {
        do {
            articles = try await service.fetchArticles()
            message = nil
        } catch is CancellationError {
            return
        } catch {
            message = "暂时无法加载内容"
        }
    }
}

异步函数挂起期间,执行器可以运行其他任务;它恢复时仍需遵守隔离规则。不要用 DispatchQueue.main.async 掩盖并发模型不清的问题。

四、取消是协作式的

调用 Task.cancel() 只会发出取消信号,不会强行终止任意代码。任务需要在合适位置响应取消:

func process(_ items: [Item]) async throws -> [ResultItem] {
    var output: [ResultItem] = []
    for item in items {
        try Task.checkCancellation()
        output.append(try await transform(item))
    }
    return output
}

网络框架通常会将任务取消映射到底层请求取消。CPU 密集循环则应定期检查取消。取消不是普通业务失败,不应显示成“服务器错误”。

五、界面生命周期与任务

SwiftUI 的 .task 会将任务与视图生命周期关联,视图消失时可以取消;依据输入值变化重启任务时,应确保旧任务取消后不会覆盖新结果。UIKit 可由控制器保存 Task 句柄,并在生命周期结束或请求被替代时取消。

对于搜索输入,可以对值去重、延迟处理,并在新查询到来时取消旧查询。仅仅取消旧任务仍不够;若底层服务不响应取消,可增加请求序号,只接受最新请求的结果。

六、用 Actor 保护共享可变数据

多个任务并发访问同一可变缓存时,actor 提供隔离边界:

actor ArticleCache {
    private var values: [Int: Article] = [:]

    func value(for id: Int) -> Article? {
        values[id]
    }

    func insert(_ article: Article) {
        values[article.id] = article
    }
}

跨 Actor 调用通常需要 await。Actor 保护内部状态免于数据竞争,但不要在 Actor 内长时间等待无关工作,否则会让需要访问该 Actor 的任务排队。

七、常见问题

  1. 不要用 Task.detached 作为普通异步调用的替代,它脱离父任务上下文和生命周期。
  2. 不要捕获控制器形成强引用循环;明确任务的所有者和取消时机。
  3. 不要假设 async 一定在后台线程运行;异步描述的是挂起能力,不是线程承诺。
  4. 不要忽略并发访问警告;它往往暴露真正的数据竞争。
  5. 错误、取消与空结果应分开建模。

八、测试异步代码

异步单元测试可直接声明为 async,使用可控的假服务验证成功、失败和取消情形。避免依赖固定 sleep 时间;由测试主动释放延迟点或提供确定输入,测试会更稳定。

九、总结

Swift 并发的关键是让任务有明确的父子关系、共享数据有隔离边界、取消能传播到实际工作,并让 UI 状态回到主执行器更新。把这些规则应用到请求、搜索和后台处理后,异步代码既易读,也更容易测试。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。