连续异步正确地从串口读取数据

9

首先声明一下,我今天刚开始尝试使用Async,所以我对自己正在做的事情的理解非常有限。

之前我是用线程从串口读取数据,将这些数据处理成要写入的数据,然后写入。我使用了1个线程读取数据并将其放入缓冲区,另一个线程从缓冲区处理数据,最后一个线程将其写入。这样,如果我点击按钮,就可以发送额外的数据。

现在,我只是想学习并确保我做的每件事都正确,所以我正在尝试从串行端口读取数据,并将该数据添加到一个多行文本框中。下面是代码:

连接到串行端口,如果成功,则调用UpdateMessageBox,该方法是异步的:

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
    }
}

这段代码不断地调用ReadLineAsync方法,并将结果添加到文本框中:
public async Task UpdateMessageBox()
{
    messageTextBox.Text += "Reading data from serial.";
    while (_serialConnected)
    {
        string message = await SerialReadLineAsync(serial);
        messageTextBox.Text += message;
    }
}

这里使用 SerialPort.BaseStream 上的 ReadAsync 方法,仅在接收到完整的一行数据(以换行符表示)时才返回数据:

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;
        }
    }
}

我是否做得正确?我能在UI线程中调用异步方法吗?Visual Studios告诉我我应该使用await,但它只是建议。
我想让其他代码在UI线程上运行,同时也要持续地读取数据,所以我不想await UpdateMessageBox 函数。如果这是一个线程,我只希望读取线程在后台运行,我只需执行 myReadThread.Start(),那么在异步情况下是否有类似的方法呢?
编辑:只是为了澄清,这段代码确实可以工作,但我想知道这样做是否是“正确”的方式。
1个回答

9
你需要更改:
private void serialConnectClick(object sender, EventArgs e)

为了

private async void serialConnectClick(object sender, EventArgs e)

将调用更改为UpdateMessageBox();

await UpdateMessageBox().ConfigureAwait(true);

UpdateMessageBox 应该使用 ConfigureAwait(true) 来捕获当前上下文(UI),否则 messageTextBox.Text += message; 将会在不同的线程上执行:

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

SerialReadLineAsync中,您可以更改:
await serialPort.BaseStream.ReadAsync(buffer, 0, 1);

...to:

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

因为SerialReadLineAsync语句与UI无关。

一般的提示是 "async all the way",这意味着任何调用async方法的方法也需要变成async并且在其内部使用await。如此往复上升整个调用堆栈。


啊,我不知道我可以将那个函数变成异步的!有没有办法让我不必await UpdateMessageBox?当我包含await关键字时,第二次按下按钮(目前编程为关闭串口)时会出现IOexceptions - Xander Luciano
将来的任何人都可以这样做!只需添加 .ConfigureAwait(false);,因此应该是 UpdateMessageBox().ConfigureAwait(false); - Xander Luciano
2
当然可以。只需小心在 UI 方法中使用 ConfigureAwait(false),否则紧随其后的语句可能会在不同的线程上执行。否则,如果您不关心使用哪个线程,请使用 ConfigureAwait(false)了解更多 - user585968

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接