Swift并发模型中的Task
Swift并发模型中的Task

Swift并发模型中的Task

在 Swift并发中:

Task 是一种用于启动、管理和控制异步操作的轻量级结构。
它不是线程,而是运行在 Swift 并发运行时中的“协程任务”。

基本功能

1、运行异步任务

Task {
    await doSomething()
}

会自动在适当的线程/调度器上运行,如果在同步函数里,想调用 async 函数,就要用 Task 包裹。

2、支持取消(可取消点)

Task 可以被取消,当它在某些挂起点(如 Task.sleep, await 网络请求)时,会主动感知被取消。

3、支持切换线程(自动调度)

它可能在不同时间切换到不同线程执行,不需要手动管理。

初始化方式

1、非结构化任务

Task {
    await doWork()
}

任务不受父任务约束,在全局执行,不会自动随View视图生命周期取消。

2、结构化任务(子任务)

await withTaskGroup(of: Void.self) { group in
    group.addTask {
        await work()
    }
}

父任务结束时,所有子任务也取消。

3、MainActor任务(保证主线程)

Task { @MainActor in
    self.value = 42
}

适合更新 UI。

4、Detached Task(完全独立任务)

Task.detached {
    await perform()
}

Task.detached 启动的任务是脱离当前上下文(actor/优先级/结构化任务)的独立任务。

等同于:

Task.detached(priority: nil, operation: { ... })

指定优先级:

Task.detached(priority: .background) {
    await loadInBackground()
}

不继承当前 actor 上下文(所以不能操作 UI,除非加 @MainActor)。

       完全独立,不受父任务影响,适合用来执行独立的后台任务。

常用方法

       Task 在 Swift Concurrency 中不仅是用来创建异步任务的,还自带一套方法和属性,用来管理任务的生命周期、状态、优先级、取消等。

       1、Task保存到变量

var tradeTask: Task<Void, Never>?   // 保存任务的变量

tradeTask = Task {
    await executeTrade()
}

2、设置优先级

Task(priority: .high) {
    await fetchData()
}

可用的优先级有

TaskPriority:
- .high
- .medium  ← 默认
- .low
- .background
- .utility
- .userInitiated

系统会根据优先级优化调度顺序,但不是绝对保证执行顺序。

3、取消任务

let task = Task {
    try await longRunningTask()
}
task.cancel()

cancel() 会发送取消信号,任务是否真正终止,取决于任务内部是否响应这个取消请求。

4、响应取消任务

在取消任务时,还需要在任务内部响应取消,使用 Task.isCancelled 或 try Task.checkCancellation()。

func longRunningTask() async throws {
    for i in 0..<100 {
        try Task.checkCancellation()
        // 做某件事
    }
}

Task.checkCancellation()用来检测是否已经被取消(Task.isCancelled == true),然后手动抛出 CancellationError,提前中止任务执行。

5、isCancelled:检查当前任务是否被取消(用于条件判断)。

if Task.isCancelled {
    print("已被取消")
}

Task.isCancelled 是 Swift Concurrency 中的一个静态属性,用于检测当前任务是否已经被取消。

当前异步任务已经收到取消请求,返回true。

当前任务还在正常执行,返回false。

6、获取结果(Task 是泛型)

let task = Task { () -> String in
    return await fetchName()
}

let result = await task.value  // 等待并获取结果

Task 是个泛型类型,可以从 .value 中获得最终返回值,更多内容请见《Swift并发模型中的Task.result属性》。

7、currentPriority:获取当前任务的优先级。

8、yield():暂时让出执行权,等待其他任务运行(任务调度优化)。

await Task.yield()

这在长时间运行的任务中,可以让系统“插个队”,提高响应性。

8、Task.sleep():暂停当前任务一段时间,属于 Task 类型的静态方法。

try await Task.sleep(nanoseconds: 1_000_000_000)  // 暂停 1 秒

挂起期间线程是空闲的,可以执行别的任务,更多内容请见《Swift并发Task.sleep》。

保存Task

Task本身是一个泛型类型:

Task<Success, Failure>
    where Failure : Error

Task有两个属性:

1、Success:成功时返回的类型。

2、Failure:失败时抛出的错误类型(必须是 Error 子类)。

例如:

Task<Int, Error> {
    return 3
}

这段代码表示:最终会产生一个Int,可能抛出错误Erro。

如果Task没有返回内容,不会抛出报错,可以使用Void和Never类型创建保存Task的变量:

var tradeTask: Task<Void, Never>?

保存Task变量后,可以随时使用 cancel() 方法取消任务:

tradeTask = Task {
    try await longRunningTask()
}
task.cancel()

总结

Task主要用于运行async异步任务。

async函数是“可挂起的函数”,Task是可以执行async函数的实体。

相关文章

1、Swift并发模型中的Task.result属性:https://fangjunyu.com/2024/12/09/swift%e5%b9%b6%e5%8f%91%e6%a8%a1%e5%9e%8b%e4%b8%ad%e7%9a%84task-result%e5%b1%9e%e6%80%a7/

2、Swift主线程:https://fangjunyu.com/2024/12/25/swift%e4%b8%bb%e7%ba%bf%e7%a8%8b/

3、Swift并发Task.sleep:https://fangjunyu.com/2025/01/10/swiftui%e5%bb%b6%e8%bf%9f%e6%93%8d%e4%bd%9csleep/

4、Swift并发模型中的Task.result属性:https://fangjunyu.com/2024/12/09/swift%e5%b9%b6%e5%8f%91%e6%a8%a1%e5%9e%8b%e4%b8%ad%e7%9a%84task-result%e5%b1%9e%e6%80%a7/

5、Swift并发模型中的TaskGroup和async let:https://fangjunyu.com/2025/05/15/swift%e5%b9%b6%e5%8f%91%e6%a8%a1%e5%9e%8b%e4%b8%ad%e7%9a%84taskgroup%e5%92%8casync-let/

扩展知识

Task与主线程的问题

Swift Concurrency 中关于 Task 是否在主线程、何时进入后台线程,很多人一开始都会有些模糊,我们来清晰梳理:

1、Task {} 会继承当前上下文(包括线程)

// 在主线程中调用
Task {
    print(Thread.isMainThread) // true
    await someAsyncWork()
    print(Thread.isMainThread) // true(可能)
}

这是结构化任务,会尽量继承当前的 actor(包括主线程 MainActor)。

但注意:await 后恢复的线程可能不是主线程!除非用 @MainActor 限定。

:Tread.isMainThread 是 Swift 提供的一个静态属性,用于判断 当前代码是否运行在主线程上,具体请见《Swift主线程》底部的扩展知识。

2、Task.detached {} 不继承主线程上下文

Task.detached {
    print(Thread.isMainThread) // false(通常在后台)
    await someAsyncWork()
}

这是“非结构化任务”,不会继承当前线程、actor 或任务优先级。

更适合用来处理不依赖 UI 的独立任务(比如下载图片、解析大数据)。

3、async/await 会换线程吗?

可能会换线程。

Task {
    print("before await:", Thread.isMainThread) // true
    await doSomethingAsync()
    print("after await:", Thread.isMainThread) // true or false(不确定)
}

await 会挂起当前任务,让出线程。

Swift 会在“可用线程池”中恢复这个任务。

恢复时不保证还是原来的线程,除非绑定到 @MainActor。

4、如何强制保持在主线程?

@MainActor
func updateUI() {
    // 一定在主线程
}

Task {
    await updateUI()  // 强制主线程
}

或者用 MainActor.run 块:

Task {
    await MainActor.run {
        // 在主线程中更新 UI
    }
}

还有一种常见的写法:

Task {
    @MainActor in
    // 这里的代码会在主线程(MainActor)上运行
}

@MainActor in 和 await MainAcotr.run是等价的,@MainActor是更简洁的语法糖。

5、Task{}可以在后台线程创建么?

Task {} 可以在后台线程中创建,而且这样做时,它会继承那个后台线程的上下文,不会自动“跳回主线程”。

代码示例:

DispatchQueue.global().async {
    print("现在在哪?", Thread.isMainThread) // false 

    Task {
        print("Task 在哪?", Thread.isMainThread) // false 仍在后台
        await doSomething()
    }
}
   

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开发者。

欢迎加入我们的 微信交流群QQ交流群,交流更多精彩内容!
微信交流群二维码 QQ交流群二维码

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注