1

I have this code:

Private Sub NewRecallLabel_TextChanged(sender As Object, e As EventArgs)
    Dim myrecalllabel As Label = TryCast(sender, Label)

    Dim SpeechSynthesizer As New SpeechSynthesizer

    SpeechSynthesizer.Speak("Ticket number " & TTSTicket & ", please proceed to counter " & TTSCounter)

End Sub

What happens is, when the TextChanged event fires, the UI freezes while performing the speak code. I know using SpeakAsync will solve the problem but when multiple TextChanged event is fired, the audio overlaps - I don't want that to happen.

Can anyone show me how could I avoid this?

carlot0820
  • 91
  • 1
  • 9

3 Answers3

1

We've established that you're using .NET 4.0, so SemaphoreSlim goes out the window.

First I'd try getting around the problem by sharing an instance of SpeechSynthesizer and using SpeakAsyncCancelAll when a new request comes in:

Private SpeechSynthesizer As New SpeechSynthesizer

Private Sub NewRecallLabel_TextChanged(sender As Object, e As EventArgs)
    Dim myrecalllabel As Label = TryCast(sender, Label)

    SpeechSynthesizer.SpeakAsyncCancelAll()
    SpeechSynthesizer.SpeakAsync("Ticket number " & TTSTicket & ", please proceed to counter " & TTSCounter)
End Sub

Oringinal answer

Since you can't use SyncLock in an async context and you want non-blocking execution, your best bet is to use a SemaphoreSlim(1, 1) to get async mutex semantics (similar to what @Gubr suggested, except async):

Private Semaphore As New SemaphoreSlim(1, 1)

Private Sub NewRecallLabel_TextChanged(sender As Object, e As EventArgs)
    Dim myrecalllabel As Label = TryCast(sender, Label)
    Dim SpeechSynthesizer As New SpeechSynthesizer

    Await Semaphore.WaitAsync()

    Try
      ' We're inside the protected region now.
      Await SpeechSynthesizer.SpeakAsync("Ticket number " & TTSTicket & ", please proceed to counter " & TTSCounter)
    Finally
      Semaphore.Release()
    End Try

End Sub

I'd also consider wiring in some form of throttling and/or auto-cancellation, because the code as it stands will not work very well if the label text changes rapidly (i.e. each subsequent speech request will have to wait for previous ones to complete).

Kirill Shlenskiy
  • 9,367
  • 27
  • 39
  • I thought it will work since lock statement is wrapped in a normal synchronous method whichis being called asynchronously. But maybe I was wrong as I said I have not tried it really. your solution seems neat – Gubr Apr 12 '16 at 04:44
  • I'm getting an error: 'WaitAsync' is not a member of 'System.Threading.SemaphoreSlim' – carlot0820 Apr 12 '16 at 04:58
  • this is so deep! Sorry I'm a newbie. – carlot0820 Apr 12 '16 at 04:59
  • @Gubr, `SyncLock` will work fine in any synchronous code regions, but you can't have an `Await` statement inside of it. Since the ultimate goal (as per the question) is non-blocking execution I chose to recommend a synchronization primitive which supports async scenarios out of the box. – Kirill Shlenskiy Apr 12 '16 at 05:00
  • @carlot0820, sounds like you're running .NET 4.0 or below. I've also just noticed that `SpeakAsync` is also a `void` (I erroneously assumed that it returned `Task`), which invalidates my above answer. I will rework it when I have a minute; for the time being please go with Gubr's suggestion. – Kirill Shlenskiy Apr 12 '16 at 05:02
  • Thank you @KirillShlenskiy for your response but the succeeding `Speak` codes must wait for the first one to finish before executing the new one - not cancel (which I think the `SpeechSynthesizer.SpeakAsyncCancelAll()` does). My bad, I think I have not mentioned that in my question. – carlot0820 Apr 12 '16 at 07:26
0

maybe using lock with asynchronous call will help it would look like this:

Private Sub NewRecallLabel_TextChanged(sender As Object, e As EventArgs)
    Dim myrecalllabel As Label = TryCast(sender, Label)
    Dim SpeechSynthesizer As New SpeechSynthesizer
        SpeechSynthesizer.SpeakAsync("Ticket number " & TTSTicket & ", please proceed to counter " & TTSCounter)
End Sub

Inside your Async call use lock:

Private messagesLock As New Object
SyncLock messagesLock
    SpeechSynthesizer.Speak("the sync one")
End SyncLock

I didn't try this but you get the idea.

Gubr
  • 324
  • 1
  • 7
  • 15
0

I was able to get what I want using a BackgroudWorker. I placed the SpeakAsync code to the BackgroudWorker and replaced the

SpeechSynthesizer.SpeakAsync("Ticket number " & TTSTicket & ", please proceed to counter " & TTSCounter)

with

      If SpeechBackgroundWorker.IsBusy = False Then

         TTSCounter = CRichTextBox.Text
         TTSTicket = TRichTextBox.Text

         SpeechBackgroundWorker.RunWorkerAsync()

         Exit Sub

      End If

It successfully queues the speeches and it doesn't affect the UI thread. Hope this helps!

carlot0820
  • 91
  • 1
  • 9