【问题标题】:C# GUI refresh and async serial port communicationC# GUI 刷新和异步串口通信
【发布时间】:2016-11-25 06:22:58
【问题描述】:

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

目前在 GUI 中的移动是由触发 GUI 下一个“页面”的绘制的 KeyEvents 完成的。但是一步(按下键后)我需要绘制新页面并通过串口发送一些命令。

命令发送是通过:

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)。如果我在这样的异步方法中这样做:

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

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

到目前为止,我已经尝试使用 BackGroundWorker Work Complete 和 Property Change。数据再次发送,但图像没有重新加载。知道如何实现吗?

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

【问题讨论】:

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


【解决方案1】:

您需要使用状态机和delegates 来实现您想要做的事情。请参阅下面的代码,我建议在除 Main 之外的单独线程中执行所有这些操作。您跟踪您所处的状态,当您收到响应时,您使用正确的回调函数对其进行解析,如果它是您所期望的,您将进入下一个发送命令状态。

private delegate void CallbackFunction(String Response);    //our generic Delegate
private CallbackFunction CallbackResponse;                  //instantiate our delegate
private StateMachine currentState = StateMachine.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);
    }
}

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

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

【讨论】:

  • 我使用了一些不同的方式,但您的帖子极大地帮助了我提出解决方案。我能够通过同步发送命令并通过异步串行处理程序接收它们来解决我的问题,我正在向其传递对象并从其中调用函数。
  • 您从哪里获得 ATRHBPCalStateMachine?
  • 啊,哎呀,很好,我正在从我编写的代码中复制并忘记重命名它。应该是StateMachine。很抱歉造成混乱。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-11
  • 2019-11-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多