C#GUI刷新和异步串口通信

时间:2016-07-21 15:10:54

标签: c# winforms serial-port async-await

我试图创建一个通过串口与硬件通信的应用程序,并将结果报告给gui。

目前通过GUI移动是由KeyEvents触发的,它触发了下一个"页面的绘制。 GUI。然而,在一步(按下键后)我需要绘制新页面并通过串口发送少量命令。

命令发送通过以下方式完成:

port.Write(data, 0, data.Length);

然后我等待DataReceivedHandler触发等待答案 - 它只是确定存在等待的数据并且正在用另一种方法处理数据。

起初我只是发送&在"绘制部件"之后,在绘制页面的函数中接收命令。然而它让它卡住 - 数据被转移,但页面没有被绘制 - 它被冻结了。

然后我做了一个异步方法:

private async void SendData()
{
  await Task.Run(() => serialClass.SendAndReceive(command));
  // process reply etc.
}

使用的是:

public void LoadPage()
{
  image = Image.FromFile(path);
  //do some stuff on image using Graphics, adding texts etc.
  picturebox1.Image = image;
  SendData();
}

它工作正常,但我需要重新加载"页面(再次调用LoadPage)。如果我在async方法中这样做:

private async void SendData()
{
  await Task.Run(() => serialClass.SendAndReceive(command));
  // process reply etc.
  LoadPage();
}

然后显然图像不会刷新,但数据将通过串口发送。是否有可能以某种方式检查异步功能是否已完成并触发我可以重新加载页面的事件?

到目前为止,我已尝试使用BackGroundWorker工作完成和属性更改。数据再次发送,但图像未重新加载。知道如何实现这个目标吗?

提前感谢您的帮助, 最好的问候

1 个答案:

答案 0 :(得分:2)

您需要使用状态机和this other post来实现您的目标。请参阅下面的代码,我建议在除Main之外的单独线程中执行所有这些操作。您可以跟踪您所处的状态,并在获得响应时使用正确的回调函数对其进行解析,如果符合您的预期,则转到下一个发送命令状态。

private delegate void CallbackFunction(String Response);    //our generic Delegate
private CallbackFunction CallbackResponse;                  //instantiate our delegate
private StateMachine currentState = ATRHBPCalStateMachine.Waiting;

SerialPort sp;  //our serial port

private enum StateMachine
{
    Waiting,
    SendCmd1,
    Cmd1Response,
    SendCmd2,
    Cmd2Response,
    Error
}

private void do_State_Machine()
{
    switch (StateMachine)
    {
        case StateMachine.Waiting:
            //do nothing
            break;
        case StateMachine.SendCmd1:
            CallbackResponse = Cmd1Response;    //set our delegate to the first response
            sp.Write("Send first command1");    //send our command through the serial port

            currentState = StateMachine.Cmd1Response;   //change to cmd1 response state
            break;
        case StateMachine.Cmd1Response:
            //waiting for a response....you can put a timeout here
            break;
        case StateMachine.SendCmd2:
            CallbackResponse = Cmd2Response;    //set our delegate to the second response
            sp.Write("Send command2");  //send our command through the serial port

            currentState = StateMachine.Cmd2Response;   //change to cmd1 response state
            break;
        case StateMachine.Cmd2Response:
            //waiting for a response....you can put a timeout here
            break;
        case StateMachine.Error:
            //error occurred do something
            break;
    }
}

private void Cmd1Response(string s)
{
    //Parse the string, make sure its what you expect
    //if it is, then set the next state to run the next command
    if(s.contains("expected"))
    {
        currentState = StateMachine.SendCmd2;
    }
    else
    {
        currentState = StateMachine.Error;
    }
}

private void Cmd2Response(string s)
{
    //Parse the string, make sure its what you expect
    //if it is, then set the next state to run the next command
    if(s.contains("expected"))
    {
        currentState = StateMachine.Waiting;
        backgroundWorker1.CancelAsync();
    }
    else
    {
        currentState = StateMachine.Error;
    }
}

//In my case, I build a string builder until I get a carriage return or a colon character.  This tells me
//I got all the characters I want for the response.  Now we call my delegate which calls the correct response
//function.  The datareceived event can fire mid response, so you need someway to know when you have the whole
//message.
private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
    string CurrentLine = "";
    string Data = serialPortSensor.ReadExisting();

    Data.Replace("\n", "");

    foreach (char c in Data)
    {
        if (c == '\r' || c == ':')
        {
            sb.Append(c);

            CurrentLine = sb.ToString();
            sb.Clear();

            CallbackResponse(CurrentLine);  //calls our correct response function depending on the current delegate assigned
        }
        else
        {
            sb.Append(c);
        }
    }
}

我会把它放在后台工作器中,当你按下按钮或其他东西时,你可以将当前状态设置为SendCmd1

按下按钮

private void buttonStart_Click(object sender, EventArgs e)
{
    if(!backgroundWorker1.IsBusy)
    {
        currentState = StateMachine.SendCmd1;

        backgroundWorker1.RunWorkerAsync();
    }
}

后台工作人员做工作事件

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    while (true)
    {
        if (backgroundWorker1.CancellationPending)
            break;

        do_State_Machine();
        Thread.Sleep(100);
    }
}

编辑:您可以使用invoke从后台工作线程更新GUI。

this.Invoke((MethodInvoker)delegate
{
    image = Image.FromFile(path);
    //do some stuff on image using Graphics, adding texts etc.
    picturebox1.Image = image;
});