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

Swift并发模型中的Task

在 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/

   

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

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

发表回复

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