如何正确取消Swiftasync/await函数

How to properly cancel Swift async/await function

我看过 Explore structured concurrency in Swift 视频和我能找到的其他相关视频/文章/书籍(swift by Sundell,hacking with swift,Ray Renderlich),但所有那里的例子非常简单——异步函数通常只有 1 个异步调用。这在实际代码中应该如何工作?

例如:

...
        task = Task {
            var longRunningWorker: LongRunningWorker? = nil

            do {
                var fileURL = state.fileURL
                if state.needsCompression {
                    longRunningWorker = LongRunningWorker(inputURL: fileURL)
                    fileURL = try await longRunningWorker!.doAsyncWork()
                }

                let urls = try await ApiService.i.fetchUploadUrls()

                if let image = state.image, let imageData = image.jpegData(compressionQuality: 0.8) {
                    guard let imageUrl = urls.signedImageUrl else {
                        fatalError("Cover art supplied but art upload URL is nil")
                    }

                    try await ApiService.i.uploadData(url: imageUrl, data: imageData)
                }

                let fileData = try Data(contentsOf: state.fileUrl)
                try await ApiService.i.uploadData(url: urls.signedFileUrl, data: fileData)

                try await ApiService.i.doAnotherAsyncNetworkCall()
            } catch {
                longRunningWorker?.deleteFilesIfNecessary()
                throw error
            }


        }
...

然后我会打电话给task.cancel()

谁负责取消什么?到目前为止我看到的示例会使用 try Task.checkCancellation(),但是对于这段代码,该行应该每隔几行出现一次 - 应该如何完成?

如果 API 服务使用 URLSession,调用将在 iOS 15 取消,但我们不使用 URLSession 代码的异步变体,因此我们必须手动取消调用。这也适用于所有长 运行 工人代码。

我也在想我可以在每个异步函数中添加这个检查,但是基本上所有异步函数都会有相同的样板代码,这又看起来是错误的,我还没有在任何视频中看到这样做.

编辑: 我已经删除了回调调用,因为它们与问题无关。

Examples I've seen so far would use try Task.checkCancellation(), but for this code that line should appear every few lines - is that how it should be done?

基本上是的。取消是完全自愿的冒险。运行时不知道取消对于 您的 特定任务意味着什么,所以它只留给您。您查看 Task.isCancelled,或者,如果您打算 throw 以防万一任务 取消,您可以调用 Task.checkCancellation.

请注意,如果在您的任务中,您正在调用(使用 try)取消时抛出的任何异步 material,则您不需要针对该 material,因为当取消抛出时,你会自动取消抛出。


说了这么多,我必须补充一点,作为脚注,您的代码非常奇怪。回调和async/await是相反的;在 Task 中执行 do/catch 并调用回调 的想法非常奇怪,我建议不要这样做。通过这样做,您基本上否定了任务的所有优势,并且使我刚才所说的关于 throw 逐渐从您的任务中溢出的事情变得不真实。

我们自己的取消逻辑的实现有两种基本模式:

  1. 使用withTaskCancellationHandler(handler:operation:)包装您的可取消异步进程。

    这在调用可取消遗留 API 并将其包装在 Task 中时很有用。这样,取消任务可以主动停止旧式 API 中的异步进程,而不是等到您到达手动 isCancelledcheckCancellation 调用。此模式适用于 iOS 13/14 URLSession API 或任何提供取消方法的异步 API。

  2. 定期检查isCancelled or try checkCancellation

    这在您使用循环执行一些手动、计算密集型过程的情况下很有用。

    许多关于处理协作取消的讨论往往集中在这些方法上,但在处理遗留可取消 API 时,上述 withTaskCancellationHandler 通常是更好的解决方案。

因此,我个人会专注于在包装一些遗留异步进程的方法中实现协作取消。通常,取消逻辑会向上渗透,通常不需要在调用链中进行进一步的额外检查,通常由您可能已经拥有的任何错误处理逻辑来处理。