froggy_
froggy_

Reputation: 43

Problems working with async Task and Textbox.Text = "Hello"

First of all, sorry because I am so new at C# and I decided to make this question because I have been choked in this for hours.

I have an GUI that works with Google Cloud Speech services and make a Speech-to-Text operation. I share with you the whole method that runs when a button is clicked:

private async Task<object> StreamingMicRecognizeAsync(int seconds)
    {
        if (NAudio.Wave.WaveIn.DeviceCount < 1)
        {
            Console.WriteLine("No microphone!");
            return -1;
        }

        GoogleCredential googleCredential;
        using (Stream m = new FileStream(@"..\..\credentials.json", FileMode.Open))
            googleCredential = GoogleCredential.FromStream(m);
        var channel = new Grpc.Core.Channel(SpeechClient.DefaultEndpoint.Host,
            googleCredential.ToChannelCredentials());
        var speech = SpeechClient.Create(channel);

        var streamingCall = speech.StreamingRecognize();

        // Write the initial request with the config.
        await streamingCall.WriteAsync(
            new StreamingRecognizeRequest()
            {
                StreamingConfig = new StreamingRecognitionConfig()
                {
                    Config = new RecognitionConfig()
                    {
                        Encoding =
                        RecognitionConfig.Types.AudioEncoding.Linear16,
                        SampleRateHertz = 48000,
                        LanguageCode = "es-ES",
                    },
                    InterimResults = true,
                }
            });

        // Read from the microphone and stream to API.
        object writeLock = new object();
        bool writeMore = true;
        var waveIn = new NAudio.Wave.WaveInEvent();
        waveIn.DeviceNumber = 0;
        waveIn.WaveFormat = new NAudio.Wave.WaveFormat(48000, 1);
        waveIn.DataAvailable +=
            (object sender, NAudio.Wave.WaveInEventArgs args) =>
            {
                lock (writeLock)
                {
                    if (!writeMore) return;
                    streamingCall.WriteAsync(
                        new StreamingRecognizeRequest()
                        {
                            AudioContent = Google.Protobuf.ByteString
                                .CopyFrom(args.Buffer, 0, args.BytesRecorded)
                        }).Wait();
                }
            };

        // Print responses as they arrive.
        Task printResponses = Task.Run(async () =>
        {
            while (await streamingCall.ResponseStream.MoveNext(default(CancellationToken)))
            {
                foreach (var result in streamingCall.ResponseStream
                    .Current.Results)
                {
                    foreach (var alternative in result.Alternatives)
                    {
                        Console.WriteLine(alternative.Transcript);
                        //Textbox1.Text = alternative.Transcript;
                    }
                }
            }
        });

        waveIn.StartRecording();
        Console.WriteLine("Speak now.");
        Result_Tone.Text = "Speak now:\n\n";
        await Task.Delay(TimeSpan.FromSeconds(seconds));

        // Stop recording and shut down.
        waveIn.StopRecording();
        lock (writeLock) writeMore = false;
        await streamingCall.WriteCompleteAsync();
        await printResponses;
        return 0;
    }

My problem is that I want to update the content of the Textbox1control but it doesn´t work. It writes perfectly the output into the console with the line Console.WriteLine(alternative.Transcript); but not into my textbox.

If someone could help I would appreciate so much his help.

Upvotes: 0

Views: 2101

Answers (3)

p3tch
p3tch

Reputation: 1495

Tasks run on seperate threads, so you must Invoke an action that will be performed on the control's thread

Textbox1.Invoke(new Action(() =>
{
    Textbox1.Text= "";
}));

Edit: For WPF, I believe the equivalent is

Textbox1.Dispatcher.Invoke(new Action(() =>
{
    Textbox1.Text= "";
}));

Upvotes: 3

pm101
pm101

Reputation: 1424

have you tried using Dispatcher.InvokeASync()?

await Dispatcher.InvokeAsync(() => {while (await streamingCall.ResponseStream.MoveNext(default(CancellationToken)))
        {
            foreach (var result in streamingCall.ResponseStream
                .Current.Results)
            {
                foreach (var alternative in result.Alternatives)
                {                       
                    Textbox1.Text = alternative.Transcript;
                }
            }
        }});    

Upvotes: 0

Jon Skeet
Jon Skeet

Reputation: 1502076

The problem is that you're using Task.Run, which means your code will be running on a thread-pool thread.

Instead of calling Task.Run(), just move that code into a separate async method:

async Task DisplayResponses(IAsyncEnumerator<StreamingRecognizeResponse> responses)
{
    while (await responses.MoveNext(default(CancellationToken)))
    {
        foreach (var result in responses.Current.Results)
        {
            foreach (var alternative in result.Alternatives)
            {
                Textbox1.Text = alternative.Transcript;
            }
        }
    }
}

Then call that method directly (without Task.Run) from code that's already on the UI thread (e.g. an event handler).

The async machinery will make sure that after the await expression, you're back on the UI thread (the same synchronization context). So the assignment to the Text property will occur on the UI thread, and all should be well.

For example:

// This would be registered as the event handler for a button
void HandleButtonClick(object sender, EventArgs e)
{
     var stream = client.StreamingRecognize();
     // Send the initial config request
     await stream.WriteAsync(...);

     // Presumably you want to send audio data...
     StartSendingAudioData(stream);

     await DisplayResponses(stream.ResponseStream);
}

Upvotes: 5

Related Questions