如何正确取消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
逐渐从您的任务中溢出的事情变得不真实。
我们自己的取消逻辑的实现有两种基本模式:
使用withTaskCancellationHandler(handler:operation:)
包装您的可取消异步进程。
这在调用可取消遗留 API 并将其包装在 Task
中时很有用。这样,取消任务可以主动停止旧式 API 中的异步进程,而不是等到您到达手动 isCancelled
或 checkCancellation
调用。此模式适用于 iOS 13/14 URLSession
API 或任何提供取消方法的异步 API。
定期检查isCancelled
or try
checkCancellation
。
这在您使用循环执行一些手动、计算密集型过程的情况下很有用。
许多关于处理协作取消的讨论往往集中在这些方法上,但在处理遗留可取消 API 时,上述 withTaskCancellationHandler
通常是更好的解决方案。
因此,我个人会专注于在包装一些遗留异步进程的方法中实现协作取消。通常,取消逻辑会向上渗透,通常不需要在调用链中进行进一步的额外检查,通常由您可能已经拥有的任何错误处理逻辑来处理。
我看过 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
逐渐从您的任务中溢出的事情变得不真实。
我们自己的取消逻辑的实现有两种基本模式:
使用
withTaskCancellationHandler(handler:operation:)
包装您的可取消异步进程。这在调用可取消遗留 API 并将其包装在
Task
中时很有用。这样,取消任务可以主动停止旧式 API 中的异步进程,而不是等到您到达手动isCancelled
或checkCancellation
调用。此模式适用于 iOS 13/14URLSession
API 或任何提供取消方法的异步 API。定期检查
isCancelled
ortry
checkCancellation
。这在您使用循环执行一些手动、计算密集型过程的情况下很有用。
许多关于处理协作取消的讨论往往集中在这些方法上,但在处理遗留可取消 API 时,上述
withTaskCancellationHandler
通常是更好的解决方案。
因此,我个人会专注于在包装一些遗留异步进程的方法中实现协作取消。通常,取消逻辑会向上渗透,通常不需要在调用链中进行进一步的额外检查,通常由您可能已经拥有的任何错误处理逻辑来处理。