- Published on
Detached Tasks in Swift
- Authors
- Name
异步地运行给定的会抛出错误的操作,作为一个新的顶级任务。
它的api是如何定义的
@discardableRusult
static func detached(
priority: TaskPriority? = nil,
operation: sending @escaping @isolated(any) () async throws -> Success
) -> Task<Success, Failure>
初一看光是这个定义就会让人很头大,遇到这种问题,我们可以慢慢来进行拆解
- 它是一个Task的类方法,传入两个参数,返回结果是
Task<Success, Failure>
,这个结果是一个泛型结构体。是SwiftConcurrency
并发框架中的核心组件,它表示一个异步任务,可以在并发环境中运行,并通过async/await
机制处理结果或错误。
- Success: 表示任务成功完成时返回的结果类型,可以是任何类型(包括Void)
- Failure: 表示任务失败时抛出的错误类型,必须符合
Error
协议 (通常是 any Error 或具体错误类型)。
Task<Success,Failure>
主要用于以下场景:
- 执行异步操作:启动一个异步任务,运行
async
函数或闭包。 - 处理并发:支持结构化并发(如
withTaskGroup
)和非结构化并发 (如Task.detached
) - 管理结果和错误: 通过
await task.value
获取成功结果,或者通过try
捕获失败错误。
常见的类型组合:
Task<Void, Never>
表示无返回值且永不失败的任务,常用于无需结果的异步操作。
Task<Void, Never> {
await someAsyncFunction()
}
Task<SomeType, any Error>
表示返回特定类型结果,可能抛出错误的异步任务。
Task<Int, any Error> {
return try await fetchNumber()
}
@discardableRusult
是一个属性(attribute),用于修饰函数的返回值,告诉编译器该函数的返回值可以被忽略,而不会触发”未使用返回值“的警告。- 参数 priority 是一个可选参数,表示这个任务的优先级
- 参数 operation:
sending @escaping @isolated(any) () async throws -> Success
Success
: 泛型返回类型,表示闭包成功执行时返回的结果类型(如 String、Int 或自定义类型)。()
: 闭包没有参数(空参数列表)。async
: 闭包是异步的,必须在 async 上下文中通过 await 调用。throws
: 闭包可能抛出错误,需要通过 try 处理。@escaping
: 闭包是逃逸闭包,意味着它可能在函数返回后被调用,存储在外部作用域中。@Sendable
: 闭包符合 Sendable 协议,保证它可以安全地在并发上下文(如不同线程或 Actor)之间传递。@isolated(any)
: 表示闭包可以在任意隔离域(Actor 或全局并发上下文)中运行,提供了灵活的隔离控制。 总结:是一个异步、可能抛出错误、返回 Success 的逃逸闭包,适合并发任务。
Discussion
If the operation throws an error, this method propagates that error. Don’t use a detached task if it’s possible to model the operation using structured concurrency features like child tasks. Child tasks inherit the parent task’s priority and task-local storage, and canceling a parent task automatically cancels all of its child tasks. You need to handle these considerations manually with a detached task. You need to keep a reference to the detached task if you want to cancel it by calling the Task.cancel() method. Discarding your reference to a detached task doesn’t implicitly cancel that task, it only makes it impossible for you to explicitly cancel the task.
如果 operation 抛出错误,Task.detached 方法会将该错误传播出去。
不要在可以使用结构化并发特性(如子任务)来建模操作时使用分离任务(detached task)。子任务会继承父任务的优先级和任务本地存储,并且取消父任务会自动取消其所有子任务。而对于分离任务,你需要手动处理这些事项。
如果你想通过调用 Task.cancel() 方法取消分离任务,必须保留对该任务的引用。丢弃对分离任务的引用并不会隐式取消该任务,只是让你无法显式取消它。
示例
import SwiftUI
struct ContentView: View {
@Binding var text: String
@State private var task: Task<String, any Error>?
var body: some View {
VStack {
Text(text)
Button("Run Detached Task") {
task = Task.detached {
try await Task.sleep(nanoseconds: 1_000_000_000)
guard !Task.isCancelled else { throw CancellationError() }
return "Task Completed!"
}
Task { // 主线程更新
do {
text = try await task!.value
} catch {
text = "Error: \(error)"
}
}
}
Button("Cancel Task") {
task?.cancel() // 手动取消
}
}
}
}
说明:
task
保留对Task.detached
的引用,允许通过task.cancel()
取消。operation
抛出CancellationError
时,错误被传播并更新到@Binding var text
。- 相比结构化并发,
Task.detached
需要手动管理取消和优先级。