I'm trying to support async-await for some existing methods with closure. But I'm not sure how to handle the task returned by the method;
open func recognitionTask(with request: SFSpeechRecognitionRequest, resultHandler: @escaping (SFSpeechRecognitionResult?, Error?) -> Void) -> SFSpeechRecognitionTask
So far, I wrote a method like below:
func recognitionTask(with request: SFSpeechRecognitionRequest) async throws -> SFSpeechRecognitionResult? {
var task: SFSpeechRecognitionTask?
let cancelTask = { task?.cancel() }
return try await withTaskCancellationHandler(
operation: {
try await withCheckedThrowingContinuation { continuation in
task = recognitionTask(with: request) { result, error in
if let error = error {
continuation.resume(throwing: error)
} else {
continuation.resume(returning: result)
}
}
}
},
onCancel: { cancelTask() }
)
}
But I couldn't figure out how to access the task object returned by the line task = recognitionTask(with: request) while calling this method. I need the task object so that I can cancel the speech recognition when needed in the app.
You asked:
Right now you have a local reference to this
taskand you also seem to suggest that you have an ivar, too. You should have just one, or the other, but not both.If this is inside a type that is actor-isolated, then the ivar is probably easiest because the actor will take care of synchronizing the access to this property.
If you use the local var approach, please note that you currently lack synchronization for that variable. You might not notice this right now, but if you turn on the more strict concurrency checks, the compiler will bring this problem to your attention. You can fix that by creating an actor to wrap the
SFSpeechRecognitionTask, but if your type is already actor-isolated, the ivar approach will take care of this automatically.Bottom line, you can use either approach, but pick one or the other.
You go on to ask:
You don't have to do that.
While you might have that ivar for other reasons (see above), you do not have to use it to cancel the speech recognition, yourself. That’s the whole point of
withTaskCancellationHandler, namely that it allows you to cancel the Swift concurrency task, and that will take care of canceling theSFSpeechRecognitionTaskfor you. You really do not have to cancel theSFSpeechRecognitionTaskdirectly, yourself.If you are doing real-time speech recognition,
withCheckedThrowingContinuationintroduces a problem. Specifically, that is designed for one-time events. As thewithCheckedThrowingContinuationdocs will tell you, that second parameter is a continuation and that …But
recognitionTask(with:resultHandler:)will call its handler multiple times. As the docs say:Now if you have overridden the default value of
shouldReportPartialResults, and changed it tofalse, thenwithCheckedThrowingContinuationwill work. But if you want to show progress of the voice recognition (which is critical feedback to the user during real-time recognition), we would prefer to make this anAsyncSequenceinstead.For example, if you have an actor wrapping your speech recognizer, you might have a method that issues a sequence of strings as the recognition proceeds:
Then you can use that sequence like so:
This way, as you are doing realtime speech recognition, the user can see the progress.
But the observation is that when I want to stop the speech recognition (whether a sequence with the progress as it goes along or just the final result, without any partial results), we cancel the Swift concurrency task, not the
SFSpeechRecognitionTask. The Swift concurrency cancelation system will take care of that (with either theonTerminationhandler for a sequence, or with thewithTaskCancellationHandlerwhen dealing with a one-time task).