Cancellation not propagated (取消未传播)

huangapple go评论85阅读模式
英文:

Cancellation not propagated

问题

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?

要在此功能中传播取消,需要执行以下修改:

```swift
func loadResource() async throws -> Resource {
    if Task.isCancelled {
        throw URLError(.cancelled)
    }
    let data = try await load()
    return try decoder.decode(Resource.self, from: data)
}
英文:

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)
}

答案1

得分: 3

在结构化并发中,如果取消了一个 Task,它的子任务也会被取消。如果你没有看到子任务被取消,以下情况可能是潜在原因之一:

  • 可能是 load 方法引入了额外的非结构化并发(即,在 load 方法内部引入了另一个 Task 对象);
  • 可能是 load 方法未使用可取消的 async 方法;
  • 可能是试图不正确地调用了 task 变量的 cancel 方法... 我们需要看到 task 变量的生命周期以及在其 Task 对象上调用 cancel 方法的位置。

但总之,如果你遵循结构化并发并在 Task 内部使用 await 方法,你将享受到取消传播(cancelation propagation),前提是(a)你保持结构化并发,而且(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()
}

不幸的是,这将具有有限的效用。如果任务在启动后被取消(由于 Actor 重新进入性,这很可能发生),你可能已经通过了这个测试,正在等待 loadResource(这很可能是在等待 load)。更重要的是,loadResource(以及它调用的 load 方法)必须都支持取消。

人们通常鼓励其他人检查 if Task.isCancelled {…} 或调用 try Task.checkCancellation()。但更重要的是确保 load 支持取消。例如,如果你正在调用 URLSessionasync 方法,如 data(for:delegate:)data(from:delegate:),它们已经支持取消。

但如果你使用了一些在 Swift 并发之前的其他 API(也许包装在 withCheckedContinuationwithUnsafeContinuation 中),那么你通常会将它包装在 withTaskCancellationHandler 中,以利用底层 API 提供的取消支持。

但要进一步提供建议,需要看到 load 方法的实现(以及你是如何取消这个顶级 task 的)。

英文:

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:

  • perhaps the load method has introduced additional unstructured concurrency (i.e., another Task object was introduced inside load method);
  • perhaps the load method is not using a cancelable async method;
  • perhaps the attempt to call 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).

huangapple
  • 本文由 发表于 2023年4月10日 18:56:28
  • 转载请务必保留本文链接:https://go.coder-hub.com/75976473.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定