等待DataReceived在不阻塞ui的情况下触发

本文关键字:情况下 ui DataReceived 等待 | 更新日期: 2023-09-27 18:11:47

我需要等待用户向串行端口读取器输入数据,然后处理数据。然而,使用这些代码会阻塞UI,这不是我想要的。关于如何确保在继续之前接收到数据或发生超时,有什么想法吗?

我使用

的原因
do
{
Thread.Sleep(1);
} while (...)

是因为没有它,代码在用户有时间更改它之前返回indata

我从主函数调用ReadFromSerial并处理那里的数据。如果出现任何问题,我希望它返回一个空字符串。

public string ReadFromSerial()
{
    try
    {
        System.IO.Ports.SerialPort Serial1 = new System.IO.Ports.SerialPort("COM1", 9600, System.IO.Ports.Parity.None, 8, System.IO.Ports.StopBits.One);
        var MessageBufferRequest = new byte[13] { ... };
        int BufferLength = 13;
        if (!Serial1.IsOpen)
        {
            Serial1.Open();
        }
        Serial1.Write(MessageBufferRequest, 0, BufferLength); //Activates the serialport reader
        indata = "";
        Stopwatch timer = new Stopwatch();
        timer.Start();
        Serial1.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);
        do
        {                    
            Thread.Sleep(1);
        } while (string.IsNullOrEmpty(indata) && timer.Elapsed.TotalSeconds < 10);
        timer.Stop();
        if (Serial1.IsOpen)
        {
            Serial1.Close();
        }
        return indata;
    }
    catch (Exception ex)
    {
        return "";
    }
}
private static string indata;
private static void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
{
    try
    {
        SerialPort sp = (SerialPort)sender;
        if (sp.BytesToRead > 0)
        {
            indata = sp.ReadExisting();
        }
    }
    catch(InvalidOperationException)
    {
        ;
    }
}

等待DataReceived在不阻塞ui的情况下触发

这就是多线程、任务、异步编程和/或事件处理程序派上用场的地方。它们都提供了一些东西来帮助你绕过这样的东西,这取决于你正在使用的对象的类型。

在这种情况下,一个好的起点是将整个接收循环作为一个单独的线程运行,然后以某种方式将接收到的数据发送回主线程。

这是一个表单的来源,基本上做了你的工作,但无论是作为ThreadTask:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }
    // Button: starts Task version
    private void button1_Click(object sender, EventArgs e)
    {
        StartReceiveTask();
    }
    // Button: starts Thread version
    private void button2_Click(object sender, EventArgs e)
    {
        StartReceiveThread();
    }

    // Start the Receive loop as a Task
    public void StartReceiveTask()
    {
        System.Threading.Tasks.Task.Run(() => receiveThreadFunc());
    }
    // Start the Receive loop as a Thread
    public void StartReceiveThread()
    {
        var thd = new System.Threading.Thread(receiveThreadFunc);
        thd.Start();
    }
    // Called when the Receive loop finishes
    public void DataReceived(string data)
    {
        // do something with the data here
    }
    // The Receive loop, used by both Thread and Task forms.
    public void receiveThreadFunc()
    {
        using (var serial1 = new System.IO.Ports.SerialPort("COM1", 9600, System.IO.Ports.Parity.None, 8, System.IO.Ports.StopBits.One))
        {
            // open serial port
            if (!serial1.IsOpen)
                serial1.Open();
            // send init command
            var initCommand = new byte[13];
            serial1.Write(initCommand, 0, initCommand.Length);
            // get start time
            DateTime start = DateTime.Now;
            // buffer for pushing received string data into
            StringBuilder indata = new StringBuilder();
            // loop until at most 10 seconds have passed 
            while ((DateTime.Now - start).TotalSeconds < 2)
            {
                if (serial1.BytesToRead > 0)
                {
                    // allocate a buffer, up to 1K in length, to receive into
                    int blen = Math.Min(1024, serial1.BytesToRead);
                    byte[] buffer = new byte[blen];
                    // read chunks of data until none left
                    while (serial1.BytesToRead > 0)
                    {
                        int rc = serial1.Read(buffer, 0, blen);
                        // convert data from ASCII format to string and append to input buffer
                        indata.Append(Encoding.ASCII.GetString(buffer, 0, rc));
                    }
                }
                else
                    System.Threading.Thread.Sleep(25);
                // check for EOL
                if (indata.Length > 0 && indata.ToString().EndsWith("'r'n"))
                    break;
            }
            if (indata.Length > 0)
            {
                // post data to main thread, via Invoke if necessary:
                string data = indata.ToString();
                if (this.InvokeRequired)
                    this.Invoke(new Action(() => { DataReceived(data); }));
                else
                    this.DataReceived(data);
            }
        }
    }
}

我选择了不碰我已经写好的东西的解决方案。相反,我在main函数中添加了这些方法。

private void StartReceiveThread()
{
    var thd = new System.Threading.Thread(receiveThreadFunc);
    thd.Start();
}
private void receiveThreadFunc()
{
    string str = Read.ReadFromSerial();
    DataReceived(str);
}
private void DataReceived(string data)
{
    //Process the data received
}