Search code examples
c#multithreadingwinformsautoresetevent

How to use AutoResetEvent in WinForms


I recently found out about messaging between Threads in my application using AutoResetEvent. In a console app I would make a while(true) loop and rely on AutoResetEvent.WaitOne() to wait for a thread to signal that e.g. data is ready to be processed with AutoResetEvent.Set(). I know how to share data between threads using a Class that has a method for the worker thread, and data elements to share between the worker thread and the main thread.

My question is how to utilize AutoResetEvent in a Winforms app, where I normally don't see a command loop. Where should I put the WaitOne() call in a Winforms app?

About sample code: the best example is an answer on this site about how to make a Console.ReadLine() with a timeout, which is basically a straightforward example on how to use signals. That example is a console app example.

When googling, the answer on my question might be using the BackgroundWorker control ?


Solution

  • Seems like the complete solution to use multithreading in Winforms is to combine BackgroundWorker and AutoResetEvent.

    If I am correct, then BackgroundWorker is like a Thread, with the advantage that it interfaces nicely with the ProgressChanged event handler for signalling from background thread to UI. We might still need AutoResetEvent for the signalling from UI to background thread. In my case, I want to use a Next button to start a new blocking call to receive data. Below is my code that I just tested.

    Question is, however, if there really is no better way than using AutoResetEvent here. Seems to work great, though.

    /// <summary>
    /// The Next button uses this to signal the BackgroundWorker
    /// to start the blocking call to Receive data
    /// </summary>
    private AutoResetEvent _SignalStartReceive  = new AutoResetEvent(false);
    
    /// <summary>
    /// To implement variable time it takes until Receive returns
    /// </summary>
    private Random _RandomTime = new Random();
    
    // Class Initializer
    public Form()
    {
        backgroundWorker_Receive.WorkerReportsProgress = true;
        backgroundWorker_Receive.RunWorkerAsync();
        return;
    }
    
    /// <summary>
    /// User presses this button when he is ready to Receive the next
    /// data packet
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void button_ReceiveNext_Click(object sender, EventArgs e)
    {
        checkBox_Receive.Checked = true;
        textBox_ReceivedContent.Text = "";
        _SignalStartReceive.Set();
        return;
    }
    
    /// <summary>
    /// User presses this button when he is ready to Receive the next
    /// data packet
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void button_ReceiveNext_Click(object sender, EventArgs e)
    {
        checkBox_Receive.Checked = true;
        textBox_ReceivedContent.Text = "";
        _SignalStartReceive.Set();
        return;
    }
    
    /// <summary>
    /// This is the worker thread, running in the background
    /// while the UI stays responsive
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void backgroundWorker_Receive_DoWork(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker worker = sender as BackgroundWorker;
        while (true)
        {
            // blocking: wait for button click
            _SignalStartReceive.WaitOne();
            // blocking: wait for datagram over network
    #if true //temporary code to simulate UdpClient.Receive()
            DateTime StartTime = DateTime.Now;
            int RandomTimeMs = 2000 + 30 * _RandomTime.Next(100);
            Thread.Sleep(RandomTimeMs);
            _ReceivedDatagram = string.Format("UDP data ... {0} ms", (DateTime.Now - StartTime).TotalMilliseconds);
    #else
            something with UdpClient.Receive();
    #endif
            // succeeded:
            worker.ReportProgress(0);//fire the event: Receive_ProgressChanged (argument does not matter)
        }
        //return; //unreachable, but would fire the Completed event
    }
    
    /// <summary>
    /// Handler for the ReportProgress() call by the BackgroundWorker Thread
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void backgroundWorker_Receive_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        textBox_ReceivedContent.Text = _ReceivedDatagram;
        checkBox_Receive.Checked = false;
        return;
    }