Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Continuously reading from serial port asynchronously properly

I'm just going to preface this by saying I just started trying to use Async today, so I have a very limited understanding of what I am doing.

I was previously using threads to read data from a serial port, process that data into data to write, and then writing it. I had 1 thread reading data and placing it into a buffer, another thread processing data from a buffer, and a final thread writing it. This way if I clicked a button, I could send additional data.

Now I am just trying to learn and ensure I am doing everything properly, so I am trying to read data from the serial port, and add that data to a multilined textbox. Here's the code:

Connect to serial port, if successful, call UpdateMessageBox which is async:

private void serialConnectClick(object sender, EventArgs e)
{
    if (!_serialConnected)
    {
        _serialConnected = SerialConnect(portCombo.Text, int.Parse(baudCombo.Text));
        if (!_serialConnected)
        {
            portCombo.SelectedIndex = 0;
            messageTextBox.Text += "Failed to connect.\r\n";
            return;
        }

        serialConnectBtn.Text = "Disconnect";
        serialStatusLabel.Text = "Serial: Connected";
        serialStatusLabel.ForeColor = Color.Blue;
        messageTextBox.Text += "Connected\r\n";
        VDIportCombo.Enabled = false;
        soundSuccess.Play();
        UpdateMessageBox(); // this is an async function
    }
}

This continuously calls ReadLineAsync and adds the result to the textbox:

public async Task UpdateMessageBox()
{
    messageTextBox.Text += "Reading data from serial.";
    while (_serialConnected)
    {
        string message = await SerialReadLineAsync(serial);
        messageTextBox.Text += message;
    }
}

And this does ReadAsync on the SerialPort.BaseStream, and only returns data when we get a full line (denoted by a newline character):

async Task<string> SerialReadLineAsync(SerialPort serialPort)
{
    byte[] buffer = new byte[1];
    string result = string.Empty;
    Debug.WriteLine("Let's start reading.");

    while (true)
    {
        await serialPort.BaseStream.ReadAsync(buffer, 0, 1);
        result += serialPort.Encoding.GetString(buffer);

        if (result.EndsWith(serialPort.NewLine))
        {
            result = result.Substring(0, result.Length - serialPort.NewLine.Length);
            result.TrimEnd('\r','\n');
            Debug.Write(string.Format("Data: {0}", result));
            result += "\r\n";
            return result;
        }
    }
}

Am I doing everything correctly? Can I call an async method from the UI thread? Visual studios is telling me I should use await, but it is just suggesting that.

I want other code to run on the UI thread while it is continuously reading, so I don't want to await the UpdateMessageBox function. If this were a thread, I would just want the read thread to operate in the background, and I would just do myReadThread.Start(), is there something similar for async?

EDIT: Just to clarify, this code does work, but I want to know if it's the "proper" way to do what I am doing.

like image 330
Xander Luciano Avatar asked Aug 03 '16 00:08

Xander Luciano


1 Answers

You need to change:

private void serialConnectClick(object sender, EventArgs e)

to

private async void serialConnectClick(object sender, EventArgs e)

...and change the call to UpdateMessageBox(); to:

await UpdateMessageBox().ConfigureAwait(true);

UpdateMessageBox should use ConfigureAwait(true) in order to capture the current context (UI) else the messageTextBox.Text += message; would execute on a different thread:

public async Task UpdateMessageBox()
{
    messageTextBox.Text += "Reading data from serial.";
    while (_serialConnected)
    {
        string message = await SerialReadLineAsync(serial).ConfigureAwait(true);
        messageTextBox.Text += message;
    }
}

In SerialReadLineAsync, you can change:

await serialPort.BaseStream.ReadAsync(buffer, 0, 1);

...to:

await serialPort.BaseStream.ReadAsync(buffer, 0, 1).ConfigureAwait(false);

...because SerialReadLineAsync statements are not related to the UI.

The general tip is "async all the way" which means any method that awaits an async method also needs to be made async and in turn awaited. Repeat this up the call-stack.

like image 182
MickyD Avatar answered Nov 14 '22 13:11

MickyD