我理解为什么当看到一系列闭包时,人们可能会想到异步序列或任务组,但我们应该注意到perform
是同步的,而不是异步的.因此,我可能会建议相关问题与其说是"我如何处理这些闭包",不如说是"我如何在SWIFT并发中利用一个缓慢的同步API".
以下是一些观察结果:
鉴于perform
是一个同步函数,您应该知道应该避免阻塞当前参与者.因此,从理论上讲,你可以把它转移到一项独立的任务中.
话虽如此,但即使是这样也不是谨慎的.例如,在WWDC 2022的《S Visualize and optimize Swift concurrency》中,苹果明确建议将阻止代码移出SWIFT并发系统:
一定要避免阻塞任务中的调用.…如果您有需要执行这些操作的代码,请将该代码移到并发线程池之外--例如,通过在DispatchQueue上运行它--并使用延续将其连接到并发世界.
见async/await: How do I run an async function within a @MainActor class on a background thread?
您现在关注的是提供给VNDetectFaceCaptureQualityRequest
和VNGenerateImageFeaturePrintRequest
的闭包.但是这些闭包是可选的,您可以使用它们各自的results
(here和here).
因此,您可能会得到如下结果:
func faceAndFeature(for url: URL) async throws -> ([VNFaceObservation], [VNFeaturePrintObservation]) {
try await withCheckedThrowingContinuation { continuation in
DispatchQueue.global().async {
let faceQualityRequest = VNDetectFaceCaptureQualityRequest()
let featurePrintRequest = VNGenerateImageFeaturePrintRequest()
let handler = VNImageRequestHandler(url: url)
do {
try handler.perform([faceQualityRequest, featurePrintRequest])
continuation.resume(returning: (faceQualityRequest.results ?? [], featurePrintRequest.results ?? []))
} catch {
continuation.resume(throwing: error)
}
}
}
}
例如,
let (faceQuality, featurePrint) = try await faceAndFeature(for: url)
print("faceQuality =", faceQuality)
print("featurePrint =", featurePrint)
It should be noted that the above will not return any results until perform
finishes all the requests. You can, alternatively, create two AsyncChannel
instances, one for faces and one for features. 例如,
let faceChannel = AsyncThrowingChannel<[VNFaceObservation], Error>()
let featureChannel = AsyncThrowingChannel<[VNFeaturePrintObservation], Error>()
func analyzeImage(at url: URL) async throws {
try await withCheckedThrowingContinuation { continuation in
DispatchQueue.global().async {
let faceQualityRequest = VNDetectFaceCaptureQualityRequest { request, error in
Task { [weak self] in
guard let self else { return }
guard error == nil, let results = request.results as? [VNFaceObservation] else {
faceChannel.fail(error ?? VisionError.invalidRequestType)
return
}
await faceChannel.send(results)
}
}
let featurePrintRequest = VNGenerateImageFeaturePrintRequest { request, error in
let results = request.results
Task { [weak self, results] in
guard let self else { return }
guard error == nil, let results = results as? [VNFeaturePrintObservation] else {
featureChannel.fail(error ?? VisionError.invalidRequestType)
return
}
await featureChannel.send(results)
}
}
let handler = VNImageRequestHandler(url: url)
do {
try handler.perform([faceQualityRequest, featurePrintRequest])
continuation.resume()
} catch {
continuation.resume(throwing: error)
}
}
}
}
然后监控这些频道:
await withThrowingTaskGroup(of: Void.self) { group in
group.addTask {
for try await observations in self.faceChannel {
…
}
}
group.addTask {
for try await observations in self.featureChannel {
…
}
}
}
最后,启动请求:
try await analyzeImage(at: url)
以下是几点注意事项:
Vision框架似乎还没有对Sendable
进行审核,因此您可能希望将其指定为@preconcurrency
,以消除有关这一点的恼人警告:
@preconcurrency import Vision
请注意,在SwiftUI中,您可以从.task
视图修改器启动这些for
-await
-in
循环,当该视图被取消时,它们将被取消.在UIKit/AppKit中,您必须保留对启动它们的Task
的引用,并在有问题的视图消失时手动引用cancel
个.