Reputation: 2499
I am trying to play with the Task
in order to understand how it works, so in my toy project, I just wanted to start a text to speech, and print the time. This is my effort:
await Task.Factory.StartNew(
() => System.Diagnostics.Debug.Print("START PLAYING {0}",
System.DateTime.Now.ToString("HH:mm:ss"))).ContinueWith(
(arg) => DependencyService.Get<ITextToSpeech>().Speak(s)).ContinueWith(
(arg) => System.Diagnostics.Debug.Print("STOP PLAYING {0}",
System.DateTime.Now.ToString("HH:mm:ss"))
);
The code is inside an async void Play_Clicked(object sender, System.EventArgs e)
event handler, but as I see, it won't await the TTS to finish and print the time right away:
START PLAYING 11:22:44
START IMPLEMENTATION 11:22:44
STOP IMPLEMENTATION 11:22:45
STOP PLAYING 11:22:45
The implementation for the dependency is just a copy/paste from the Xamarin's tutorial on TTS:
using Xamarin.Forms;
using AVFoundation;
[assembly: Dependency(typeof(Testers.iOS.TextToSpeechImplementation))]
namespace Testers.iOS
{
public class TextToSpeechImplementation : ITextToSpeech
{
public TextToSpeechImplementation() { }
public void Speak(string text)
{
System.Diagnostics.Debug.Print("START IMPLEMENTATION {0}", System.DateTime.Now.ToString("HH:mm:ss"));
var speechSynthesizer = new AVSpeechSynthesizer();
var speechUtterance = new AVSpeechUtterance(text)
{
Rate = AVSpeechUtterance.MaximumSpeechRate / 2.8f,
Voice = AVSpeechSynthesisVoice.FromLanguage(App.current_lang),
PreUtteranceDelay = 0.5f,
PostUtteranceDelay = 0.0f,
Volume = 0.5f,
PitchMultiplier = 1.0f
};
speechSynthesizer.SpeakUtterance(speechUtterance);
System.Diagnostics.Debug.Print("STOP IMPLEMENTATION {0}", System.DateTime.Now.ToString("HH:mm:ss"));
}
}
}
with its interface defined as
using System;
namespace Testers
{
public interface ITextToSpeech
{
void Speak(string text);
}
}
I still am grasping this whole async
/await
concept, so I am obviously missing something important here.
Any help would be appreciated!
Upvotes: 1
Views: 702
Reputation: 74174
You use a TaskCompletionSource
along with the DidFinishSpeechUtterance
handler to determine when the speech output is finished.
Note: The DidFinishSpeechUtterance
handler is auto-assigning a AVSpeechSynthesizerDelegate
, so you can skip the Xamarin handler wrappers and directly create/use your own delegate (that is needed for some use-cases)
await speechSynthesizer.SpeakUtteranceAsync(speechUtterance, cancelToken);
public static class AClassyClass
{
public static async Task SpeakUtteranceAsync(this AVSpeechSynthesizer synthesizer, AVSpeechUtterance speechUtterance, CancellationToken cancelToken)
{
var tcsUtterance = new TaskCompletionSource<bool>();
try
{
synthesizer.DidFinishSpeechUtterance += OnFinishedSpeechUtterance;
synthesizer.SpeakUtterance(speechUtterance);
using (cancelToken.Register(TryCancel))
{
await tcsUtterance.Task;
}
}
finally
{
synthesizer.DidFinishSpeechUtterance -= OnFinishedSpeechUtterance;
}
void TryCancel()
{
synthesizer?.StopSpeaking(AVSpeechBoundary.Word);
tcsUtterance?.TrySetResult(true);
}
void OnFinishedSpeechUtterance(object sender, AVSpeechSynthesizerUteranceEventArgs args)
{
if (speechUtterance == args.Utterance)
tcsUtterance?.TrySetResult(true);
}
}
}
Note: Xamarin.Essentials
includes this flow using TaskCompletionSource and provides a TextToSpeech.SpeakAsync
providing the same feature
Re: https://learn.microsoft.com/en-us/xamarin/essentials/text-to-speech
Upvotes: 1