在 Swift Concurrency 中:
Task 是一种用于启动、管理和控制异步操作的轻量级结构。
它不是线程,而是运行在 Swift 并发运行时中的“协程任务”。
基本用法
异步任务:
Task {
await doSomething()
}
会自动在适当的线程/调度器上运行。
如果在同步函数里,想调用 async 函数,就要用 Task 包裹。
设置优先级:
Task(priority: .high) {
await fetchData()
}
可用的优先级有:
TaskPriority:
- .high
- .medium ← 默认
- .low
- .background
- .utility
- .userInitiated
系统会根据优先级优化调度顺序,但不是绝对保证执行顺序。
可取消任务:
let task = Task {
try await longRunningTask()
}
task.cancel()
cancel() 会发送取消信号,任务是否真正终止,取决于任务内部是否响应这个取消请求。
因此,在取消任务时,还需要在任务内部响应取消,使用 Task.isCancelled 或 try Task.checkCancellation()。
func longRunningTask() async throws {
for i in 0..<100 {
try Task.checkCancellation()
// 做某件事
}
}
Task.checkCancellation()用来检测是否已经被取消(Task.isCancelled == true),然后手动抛出 CancellationError,提前中止任务执行。
Task.isCancelled
Task.isCancelled 是 Swift Concurrency 中的一个静态属性,用于检测当前任务是否已经被取消。
当前异步任务已经收到取消请求,返回true。
当前任务还在正常执行,返回false。
获取结果(Task 是泛型):
let task = Task { () -> String in
return await fetchName()
}
let result = await task.value // 等待并获取结果
Task 是个泛型类型,可以从 .value 中获得最终返回值。
方法和属性
Task 在 Swift Concurrency 中不仅是用来创建异步任务的,还自带一套方法和属性,用来管理任务的生命周期、状态、优先级、取消等。
1、Task.init(priority:operation:):创建结构化任务。
2、Task.detached(priority:operation:):创建非结构化任务(不继承上下文)。
3、cancel():取消任务(标记任务为取消)。
4、isCancelled:检查当前任务是否被取消(用于条件判断)。
5、checkCancellation():抛出 CancellationError,用于强制中止。
6、sleep(nanoseconds:):异步延迟任务(非阻塞)。
7、yield():暂时让出执行权,等待其他任务运行(任务调度优化)。
8、currentPriority:获取当前任务的优先级。
9、Priority:.high .medium .low .background .utility .userInitiated 等。
常见示例
1、创建任务 + 获取结果
let task = Task {
return await fetchData()
}
let result = await task.value // 获取返回值
2、创建任务并取消它
let task = Task {
try await longRunningTask()
}
task.cancel()
3、检查取消状态(非强制)
if Task.isCancelled {
print("已被取消")
}
4、主动抛出取消错误(强制退出)
for i in 0..<100 {
try Task.checkCancellation() // 抛出 CancellationError
doSomething()
}
5、暂停当前任务(非阻塞)
try await Task.sleep(nanoseconds: 1_000_000_000) // 睡1秒
6、暂时让出线程(调度优化)
await Task.yield()
这在长时间运行的任务中,可以让系统“插个队”,提高响应性。
Task.detached
Task.detached 是用来启动一个独立的异步任务的方法。
Task.detached 启动的任务是脱离当前上下文(actor/优先级/结构化任务)的独立任务。
不会继承调用者的:actor 隔离(actor context)、任务优先级和任务层级(不是子任务)。
代码示例:
Task.detached {
// 这里是独立的异步环境
await doSomething()
}
等同于:
Task.detached(priority: nil, operation: { ... })
指定优先级:
Task.detached(priority: .background) {
await loadInBackground()
}
1、不继承当前 actor 上下文(所以不能操作 UI,除非加 @MainActor)。
2、完全独立,不受父任务影响。
3、适合用来执行独立的后台任务。
和Task的区别:
1、Task在主线程,Task.detached不在主线程。
2、Task可以直接更新UI,Task.detached无法直接更新UI,需要切回主线程。
3、Task可以继承调用位置的上下文,如优先级和actor,Task.detached则完全脱离。
4、Task用于UI操作,需要父任务控制的任务,Task.detached拥有独立后台逻辑,不影响主线程的任务。
Task.sleep
Task.sleep 是 Swift Concurrency 提供的一个异步挂起函数,用来让当前任务暂停一段时间,属于 Task 类型的静态方法。
try await Task.sleep(nanoseconds: 1_000_000_000) // 暂停 1 秒
它只能在 async 函数中使用,并且是非阻塞的(不会阻塞线程)。
代码示例:
Task {
print("开始任务")
try await Task.sleep(nanoseconds: 2_000_000_000)
print("两秒后执行")
}
Task {} 创建了一个新的异步任务。
Task.sleep(…) 让当前任务“挂起”2秒。
挂起期间线程是空闲的,可以执行别的任务。
Task.sleep和Thread.sleep()的区别:
1、Task.sleep:类型是异步挂起,不会阻塞线程,并且支持取消。
2、Thread.sleep:类型是同步阻塞,会阻塞当前现场,不可取消。
所以,在 Swift Concurrency 中,应尽量避免用 Thread.sleep(),而应该使用 Task.sleep()。
取消Task.sleep代码示例:
let task = Task {
try await Task.sleep(nanoseconds: 5_000_000_000)
print("我醒了")
}
Task {
try await Task.sleep(nanoseconds: 1_000_000_000)
task.cancel() // 1 秒后取消
print("取消任务")
}
输出:
取消任务
因为 Task.sleep 会响应取消,所以任务没有等完 5 秒就结束了。
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
}
}
5、Task{}可以在后台线程创建么?
Task {} 可以在后台线程中创建,而且这样做时,它会继承那个后台线程的上下文,不会自动“跳回主线程”。
代码示例:
DispatchQueue.global().async {
print("现在在哪?", Thread.isMainThread) // false
Task {
print("Task 在哪?", Thread.isMainThread) // false 仍在后台
await doSomething()
}
}
相关文章
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/