简体   繁体   中英

Wait for DataReceived to fire without blocking the ui

I need to wait for the user to input data to the serialport reader and then process the data. However, using this code blocks the UI which is not what I want. Any ideas on how to make sure that data is received or a timeout has occured before continuing?

The reason I use

do
{
Thread.Sleep(1);
} while (...)

is because without it the code return indata before the user has time to change it.

I call ReadFromSerial from the main function and process the data there. If anything goes wrong I want it to return an empty string.

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

This is where multi-threading, tasks, async programming and/or event handlers comes in handy. All of them offer something to help you get around stuff like this, depending on the types of objects you're using.

A good starting point in this case would be to run the whole receive loop as a separate thread, then send the received data back to the main thread in some fashion.

Here's the source of a form that does basically what yours does, but either as a Thread or a Task :

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

I went with solution not to touch what I had already written. Instead I added these methods in my main function.

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
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM