将异步任务包装成 DispatchWorkItem 在 Swift 中使其可取消?

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

Wrap async task into DispatchWorkItem in Swift to make it cancellable?

问题

我有一个带有异步方法的类。所有方法都非常简单:

class SomeClass {
   func someFunc(params: SomeParams, completion: ((SomeResult) -> ())?) {
      ... // 一些代码,在其中异步调用完成回调,不仅使用 DispatchQueue.async
   }
   ...//其他方法
}

现在我需要添加取消这些任务的可能性,但 SomeClass 不负责此操作,所以此类代码应该放在 SomeClass 外部的另一个类中。他们建议使用 DispatchWorkItem,但如何正确包装这样的异步调用呢?

注意:有关如何将 DispatchQueue.global().async 替换为 DispatchWorkItem 的问题和答案,但不是关于如何包装现有任务的问题。

英文:

I have a class with async methods. All the methods are the simplest:

class SomeClass {
   func someFunc(params: SomeParams, completion: ((SomeResult) -> ())?) {
      ... //some code where completion is called asynchronously, not only with DispatchQueue.async
   }
   ...//other methods
}

Now I need to add a possibility to cancel such these tasks but SomeClass is not responsible for it so such code should be placed into another class outside SomeClass. They recommend to use DispatchWorkItem but how wrap such async calls into it correctly?

Note: there are questions and answers how to replace DispatchQueue.global().async with DispatchWorkItem, not about wrapping existing tasks

答案1

得分: 1

我知道你已经解决了你的问题,但是为了日后的读者并回答标题中的问题,即是否可以将异步任务包装成 Swift 中的 DispatchWorkItem 以使其可取消?答案是,通常情况下,仅仅将其包装在 DispatchWorkItem 中是不够的。正如文档所告诉我们(重点强调):

取消会导致未来尝试执行工作项立即返回。取消不会影响已经开始执行的工作项。

如果你取消了已经启动的 DispatchWorkItem,它只会设置该 DispatchWorkItemisCancelled 属性,你可以在某些计算密集型例程中检查这个属性。但对于许多异步任务(比如网络请求),这将具有有限的实用性。

更糟糕的是,DispatchWorkItem 不适合包装一个本身就是异步的任务。DispatchWorkItem 通常会在异步任务被启动后立即完成工作项(除非你使用信号量之类的笨拙而脆弱的方法,这样做最好被视为反模式)。

你真的需要在 someFunc 内部包含取消逻辑。从历史上看,我们经常使用基于 GCD 框架的 Operation 子类,因为我们可以覆盖 cancel 以触发异步任务的取消。或者现在我们可能会使用Swift 并发的自动取消传播功能的 async-await。或者,你似乎已经在Combine中实现了这一点。

所以,归根结底,这取决于 someFunc 做了什么,但是 DispatchWorkItem 通常具有有限的实用性,尽管它具有(非常基本的)取消支持。

英文:

I know you’ve solved your problem already, but for the sake of future readers and in answer to the titular question whether you can “Wrap async task into DispatchWorkItem in Swift to make it cancellable?”, the answer is that, no, simply wrapping it inside a DispatchWorkItem is generally not sufficient. As the documentation tells us (emphasis added):

> Cancellation causes future attempts to execute the work item to return immediately. Cancellation does not affect the execution of a work item that has already begun.

If you cancel a DispatchWorkItem that you have already started, it merely sets isCancelled property of that DispatchWorkItem that you could check within some computationally intensive routine. But for many asynchronous tasks (like network requests), that would be of limited utility.

Worse, DispatchWorkItem is not well suited to wrap a task that is, itself, asynchronous. The DispatchWorkItem will generally finish the work item as soon as the asynchronous task has been launched, not waiting for it to complete (unless you do something kludgy and brittle, like using semaphores; so much so that it would best be considered an anti-pattern).

You really need to incorporate the cancelation logic within someFunc. Historically, we often reached for Operation subclasses (build upon the GCD framework) because we can then override cancel to trigger the cancelation of the asynchronous task. Or nowadays we might reach for the automatic cancelation propagation offered by Swift concurrency’s async-await. Or, you appear to have achieved this with Combine.

So, bottom line, it depends upon what someFunc was doing, but DispatchWorkItem is often of limited utility, despite its (very basic) cancelation support.

huangapple
  • 本文由 发表于 2023年6月2日 13:40:45
  • 转载请务必保留本文链接:https://go.coder-hub.com/76387403.html
匿名

发表评论

匿名网友

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

确定