I am using Apple's Vision framework to run multiple detection requests against one image.
我正在使用苹果的Vision框架对一张图像运行多个检测请求。
VNImageRequestHandler.perform()
schedules multiple requests (likely in a separate thread). Completion handlers are defined for the individual requests. In the end, results should be assigned to a class var.
VNImageRequestHandler.Perform()调度多个请求(可能在单独的线程中)。完成处理程序是为各个请求定义的。最后,结果应该分配给一个类var。
From my understanding, continuations are the best way to convert completion handler based APIs to async code. However, continuations can only be resumed once.
根据我的理解,延续是将基于完成处理程序的API转换为异步代码的最佳方式。但是,续订只能恢复一次。
AsyncStream
seems like a more viable solution. However, results can arrive in any order and I only have a static number of requests.
AsyncStream似乎是一个更可行的解决方案。然而,结果可以以任何顺序到达,而我只有固定数量的请求。
What are the most idiomatic async paradigms to use with the following code?
与以下代码一起使用的最常用的异步范例是什么?
// non-async code
let handler = VNImageRequestHandler(url: url)
let faceQualityRequest = VNDetectFaceCaptureQualityRequest { request, err in
guard err == nil else { return }
self.faceDetectionResults = request.results
}
let featurePrintRequest = VNGenerateImageFeaturePrintRequest { request, err in
guard err == nil else { return }
self.featurePrintResults = request.results
}
try handler.perform([faceQualityRequest, featurePrintRequest])
更多回答
I would use an AsyncThrowingStream or a TaskGroup
我会使用AsyncThrowingStream或TaskGroup
Queue , operation Queue and completion handler for queue, you can use from queuer, it's very simple: github.com/FabrizioBrancati/Queuer
用于队列的队列、操作队列和完成处理程序,可以从队列中使用,非常简单:githorb.com/FabrizioBrancati/Queuer
优秀答案推荐
I understand why, when seeing a series of closures, one might jump to the idea of asynchronous sequences or task groups, but we should note that perform
is synchronous, not asynchronous. So, I might suggest that the relevant question is less “what do I do with these closures”, but rather, “how do I avail myself of a slow, synchronous API within Swift concurrency”.
我理解为什么当看到一系列闭包时,人们可能会想到异步序列或任务组,但我们应该注意到Performance是同步的,而不是异步的。因此,我可能会建议相关问题与其说是“我如何处理这些闭包”,不如说是“我如何在SWIFT并发中利用一个缓慢的同步API”。
A few observations:
几点意见:
Given that perform
is a synchronous function, you should be aware that you should avoid blocking the current actor. So, you theoretically could move it into a detached task.
Having having been said, even that is not prudent. E.g., in WWDC 2022’s Visualize and optimize Swift concurrency, Apple explicitly advises moving the blocking code out of the Swift concurrency system:
Be sure to avoid blocking calls in tasks. … If you have code that needs to do these things, move that code outside of the concurrency thread pool – for example, by running it on a DispatchQueue – and bridge it to the concurrency world using continuations.
See async/await: How do I run an async function within a @MainActor class on a background thread?
You are focusing on the closures provided to VNDetectFaceCaptureQualityRequest
and VNGenerateImageFeaturePrintRequest
. But those closures are an optional convenience and you can instead just use their respective results
(here and here).
So, you might end up with something like:
因此,您可能会得到如下结果:
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)
}
}
}
}
E.g.,
例如,
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. E.g.,
需要注意的是,在Performance完成所有请求之前,上面不会返回任何结果。或者,也可以创建两个AsyncChannel实例,一个用于面,另一个用于特征。例如,
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)
}
}
}
}
And then monitor those channels:
然后监控这些频道:
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 {
…
}
}
}
And finally, start the request:
最后,开始请求:
try await analyzeImage(at: url)
A few notes:
以下是几点注意事项:
The Vision framework does not appear to be audited for Sendable
yet, so you might want to designate it as @preconcurrency
to silence annoying warnings about this:
@preconcurrency import Vision
Note, in SwiftUI you can launch these for
-await
-in
loops from the .task
view modifier and they will be canceled when the view is dismissed. In UIKit/AppKit, you will have to keep your own reference to the Task
that launched these and manually cancel
them when the view in question disappears.
更多回答
Thanks for this truly helpful and detailed answer. Your post also covers further questions that have been floating in my mind. Impressive!
谢谢你这个真正有帮助和详细的回答。你的帖子还涵盖了我脑海中浮现的更多问题。令人印象深刻!
我是一名优秀的程序员,十分优秀!