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

C#GUI刷新和异步串行端口通信,c#,winforms,serial-port,async-await,C#,Winforms,Serial Port,Async Await,我正在尝试创建一个应用程序,它通过串口与硬件通信,并将结果报告给gui 当前通过GUI的移动是由KeyEvents完成的,它触发GUI下一个“页面”的绘制。然而,在一个步骤中(按键后),我需要绘制新的页面并通过串行端口发送一些命令 命令发送通过以下方式完成: port.Write(data, 0, data.Length); 然后我通过等待DataReceivedHandler触发来等待答案——它只是指出有数据等待,并且数据正在用另一种方法处理 起初,我只是将发送和接收命令放在函数中,在“绘制

我正在尝试创建一个应用程序,它通过串口与硬件通信,并将结果报告给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)。如果我在异步方法中这样做:

private async void SendData()
{
  await Task.Run(() => serialClass.SendAndReceive(command));
  // process reply etc.
  LoadPage();
}
显然,图像不会被刷新,尽管数据将通过串行端口发送。是否有可能以某种方式检查异步函数是否已完成,并触发一个可以重新加载页面的事件

到目前为止,我已经尝试使用BackGroundWorker完成工作并更改属性。数据再次发送,但图像没有重新加载。你知道我怎样才能做到吗

提前感谢您的帮助,
致以最诚挚的问候

您需要使用状态机并实现您正试图实现的目标。请参阅下面的代码,我建议在除Main之外的单独线程中执行所有这些操作。跟踪所处的状态,当收到响应时,使用正确的回调函数对其进行解析,如果它是您期望的状态,则转到下一个send命令状态

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

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

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

停止使用
async-void
。停止使用
async-void
。我使用了一种稍微不同的方法,但您的帖子极大地帮助我提出了解决方案。我可以通过同步发送命令并通过异步串行处理程序接收命令来解决问题,我向异步串行处理程序传递对象并从中调用函数。我使用了一种稍微不同的方法,但您的帖子极大地帮助我提出了解决方案。我可以通过同步发送命令并通过异步串行处理程序接收命令来解决问题,我向异步串行处理程序传递对象并从中调用函数。