取消不会通过这里的服务类传播
let task = Task<Service.Resource, Error> {
if Task.isCancelled {
throw URLError(.cancelled)
}
return try await service.loadResource()
}
需要对这个函数做什么修改来传播取消?
func loadResource() async throws -> Resource {
let data = try await load()
return try decoder.decode(Resource.self, from: data)
}
在结构化并发中,如果取消一个Task
,它的子进程也将被取消。如果您没有看到取消的孩子,下面的一个可能是罪魁祸首:
- 可能
load
方法引入了额外的非结构化并发性(即在load
方法中引入了另一个Task
对象); - 可能
load
方法没有使用可取消的async
方法; - 也许尝试调用
task
变量的cancel
是错误的…我们需要查看这个task
变量的生命周期,以及在Task
对象上调用cancel
方法的位置。
但是,底线是,如果(a)您在Task
中保持结构化并发性,您将享受取消传播;(b)你await
一个支持取消的方法
作为题外话,考虑以下内容:
let task = Task<Service.Resource, Error> {
if Task.isCancelled {
throw URLError(.cancelled)
}
return try await service.loadResource()
}
可以简化为:
let task = Task<Service.Resource, Error> {
try Task.checkCancellation()
return try await service.loadResource()
}
不幸的是,这将是有限的效用。如果任务在启动后被取消(很可能是由于参与者重入),那么您可能已经通过了这个测试,等待loadResource
(它很可能正在等待load
)。更重要的是loadResource
(以及它调用的load
方法)必须都支持取消。
人们通常会鼓励别人检查if Task.isCancelled {…}
或呼叫try Task.checkCancellation()
。但更重要的是,确保load
支持取消。例如,如果你正在调用URLSession
的async
方法,例如data(for:delegate:)
或data(from:delegate:)
,它们已经支持取消。
但是如果你在使用Swift并发之前的其他API(可能是在withCheckedContinuation
或withUnsafeContinuation
中包装的东西),那么你通常会将其包装在withTaskCancellationHandler
中,利用底层API提供的任何取消支持。
但是,如果没有看到load
方法的实现(以及如何取消这个顶级task
),很难进一步提出建议。