Cancellation is not propagated through the service class here
let task = Task<Service.Resource, Error> {
if Task.isCancelled {
throw URLError(.cancelled)
}
return try await service.loadResource()
}
What modification is required on this function to propagate cancellation?
func loadResource() async throws -> Resource {
let data = try await load()
return try decoder.decode(Resource.self, from: data)
}
Within structured concurrency, if you cancel a Task
, its children will be canceled, too. If you are not seeing the children canceled, one of the following is a likely culprit:
load
method has introduced additional unstructured concurrency (i.e., another Task
object was introduced inside load
method);load
method is not using a cancelable async
method;cancel
of the task
variable was done incorrectly … we would need to see the lifecycle of this task
variable and where you called cancel
method on its Task
object.But, bottom line, you enjoy cancelation propagation if (a) you remain with structured concurrency inside your Task
; (b) you await
a method that supports cancelation.
As an aside, consider the following:
let task = Task<Service.Resource, Error> {
if Task.isCancelled {
throw URLError(.cancelled)
}
return try await service.loadResource()
}
That can be simplified to:
let task = Task<Service.Resource, Error> {
try Task.checkCancellation()
return try await service.loadResource()
}
Unfortunately, this will be of limited utility. If the the task is canceled after it has started (and because of actor-reentrancy, this is likely), you are probably past this test, awaiting the loadResource
(which is likely awaiting load
). What is more important is that loadResource
(and the load
method that it calls) must both support cancelation.
People will commonly encourage others to check if Task.isCancelled {…}
or call try Task.checkCancellation()
. But more importantly, make sure that load
supports cancelation. E.g., if you are calling the async
methods of URLSession
, such as data(for:delegate:)
or data(from:delegate:)
, they support cancelation already.
But if you are using some other other API that predates Swift concurrency (perhaps something wrapped in a withCheckedContinuation
or withUnsafeContinuation
), then you would often wrap that in a withTaskCancellationHandler
, taking advantage of whatever cancelation support that underlying API provides.
But again, it is hard to advise further without seeing the implementation of the load
method (and how you are canceling this top-level task
).