在 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()
}
}
