Continuously reading from serial port asynchronously properly
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 await
s an async
method also needs to be made async
and in turn await
ed. Repeat this up the call-stack.
Xander Luciano
JavaScript & C# developer, working to improve CAD + CAM engineering. Core focus: Vue.js, and Three.js, and Electron.
Updated on July 20, 2022Comments
-
Xander Luciano almost 2 years
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 theSerialPort.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
theUpdateMessageBox
function. If this were a thread, I would just want the read thread to operate in the background, and I would just domyReadThread.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.
-
Xander Luciano almost 8 yearsAh, didn't know I could make that function async! Is there a way I don't have to
await
theUpdateMessageBox
? When I include theawait
keyword, I getIOexceptions
when I press the button a second time (which currently is programmed to close the serial port) -
Xander Luciano almost 8 yearsFor anyone in the future, you can! Just add
.ConfigureAwait(false);
so, it should beUpdateMessageBox().ConfigureAwait(false);
-
MickyD almost 8 yearsYou sure can. Just be careful about using
ConfigureAwait(false)
during a UI method else the statement immediately following could potentially executed on a different thread. Otherwise, if you don't care about what thread to use, useConfigureAwait(false)
. Tell me more