Search code examples
swiftcommand-line-interfacedispatchsemaphore

DispatchSemaphore not waiting


In my command line app, DispatchSemaphore doesn't wait despite initializing to zero, decrementing with "wait" and incrementing with "signal". I don't want to use RunLoop.main.run().

import Foundation
import AVFoundation

var synthesizer = AVSpeechSynthesizer()
let sema = DispatchSemaphore(value: 0)

func say(_ line: String) {
    let utterance = AVSpeechUtterance(string: line)
    synthesizer.speak(utterance)
    sema.signal() // increment
}

say("hello")
sema.wait()  // decrement
//RunLoop.main.run()

Solution

  • AVSpeechSynthesizer.speak(_:) is documented to only "[add] the utterance you specify to the speech synthesizer’s queue"; the synthesizer doesn't necessarily start speaking right away, nor does the speak(_:) call wait until the synthesizer is done speaking before it returns.

    In effect, synthesizer.speak(utterance) returns immediately, which signals your semaphore right away. (Because the call to say is also synchronous, this happens before you ever reach sema.wait(), which means the semaphore could never wait.) If this is the entirety of your code and you're running it as a script, the omission of RunLoop.main.run() means that your program also ends right away, and the synthesizer never gets the opportunity to speak. (The synthesizer prepares and plays the audio from a background thread, speak(_:) just kicks off the process; if the main thread exits without waiting, the whole program terminates and that background thread won't get to complete.)

    To be informed of speech events (e.g., when the synthesizer starts and stops speaking), you'll need to create a type that conforms to the AVSpeechSynthesizerDelegate protocol, and set an instance of that type as the synthesizer's delegate. You can then respond to events as necessary.


    As an aside, DispatchSemaphore is generally not recommended for synchronization purposes like this, because as an extremely low-level synchronization tool, it can lead to priority inversions (and if used incorrectly, very easily deadlock). If you expand on why you're trying to use a semaphore, we can give a more targeted suggestion on how to best replace it.