-1

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 ?

Community
  • 1
  • 1
Roland
  • 4,619
  • 7
  • 49
  • 81
  • Could you please post a small, yet complete sample of what you're trying to do? – Yuval Itzchakov Mar 11 '15 at 15:33
  • 1
    It is not clear what are you asking about. You can use `AutoResetEvent` in those places, where you need an event for multithreading synchronization, regardless of underlying platform: winforms, WPF, console app or a class library. – dymanoid Mar 11 '15 at 15:39
  • It is the kind of hack you need in a console mode app. But *not* in a Winforms app, it already solves the producer-consumer problem. Simply call Control.BeginInvoke() from a worker thread. Like BackgroundWorker does. – Hans Passant Mar 11 '15 at 15:44
  • Answer would be simple - it's different. There is no what you call command loop, but rather message processing loop. `Wait..` can still be used: to wait in other than UI thread or to block UI thread (not recommended). So real question is what you are trying to do (`AutoResetEvent` may be a very wrong solution to your problem). – Sinatr Mar 11 '15 at 15:45
  • OK, if you all tell me that in `WinForm` apps I should use `BackgroundWorker` instead of the `AutoResetEvent` , then my question is very useful, at least for myself. No matter how many people downvote my question without explanation :-) – Roland Mar 11 '15 at 15:51
  • The main UI thread of an application is a single-threaded apartement, that has an implied and expected contract of *never waiting*. You generally never want to use synchronization primitives like events, mutexs, semaphores, etc. on the UI thread. BackgroundWorker is a *much* better idea. You might be able to use async/await or `Task`s but you'll need to provide more detail. – Peter Ritchie Mar 11 '15 at 15:51
  • 1
    @Roland downvoting on SO is *rarely* tied to usefulness of the question or the answer, unfortunately. Don't take it personally. – Peter Ritchie Mar 11 '15 at 15:52
  • There are probably thousands of such questions in here, all with the answer 'You must not wait in a GUI event-handler'. – Martin James Mar 11 '15 at 16:26
  • @MartinJames Right, but my question is not asking for that answer, but on `how` to get the required multithreading done right, or on how to apply multithreading principles that work in a console, in a winform. – Roland Mar 12 '15 at 16:53

3 Answers3

2

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;
}
Roland
  • 4,619
  • 7
  • 49
  • 81
1

From your question I understand that your UI thread should wait for an event.

The UI thread of a WinForms application must pass control to the operating system to allow user interaction, so waiting for an AutoResetEvent is not suitable. If you want to communicate with the UI thread, you can use:

bool ret = (bool)myFormInstance.Invoke(
    new Func<DataClass, bool>(myFormInstance.DoStuff), 
    myData);

public class MyForm : Form
{
    public bool DoStuff(DataClass data)
    {
        ....
    }
}

You can also use BeginInvoke to run code on the UI thread asynchronously.

If you want things the other way around (UI thread tells worker thread to start processing) you can use the BackgroundWorker and there should be plenty of tutorials about that.

C.Evenhuis
  • 25,996
  • 2
  • 58
  • 72
  • Is `Invoke()` in this way easier than using the `BackgroundWorker` control? Does `DoStuff` run in another thread? As the Winform controls are single-threaded, then it would be dificult to set values to labels etc directly from `DoStuff()`. – Roland Mar 11 '15 at 15:54
  • `Control.Invoke` makes `DoStuff` run on the thread the control was created on, so from `DoStuff` you can safely update your UI. These calls are queued and executed once the operating system gets control. – C.Evenhuis Mar 11 '15 at 16:03
  • I don't know which one you would consider _easier_ - but you use `BackgroundWorker` to start a background operation, and `Invoke` to notify the UI. They are completely different approaches. – C.Evenhuis Mar 11 '15 at 16:04
  • This solution states that `AutoResetEvent` is not suitable. But in combination with a `BackgroundWorker`, it is entirely suitable.On the other hand, perhaps `Invoke` can be useful too, good explanation. I will try that some day. Thanks, +1 – Roland Mar 20 '15 at 16:53
1

Yes, use BackgroundWorker instead.

Sinatr
  • 20,892
  • 15
  • 90
  • 319
  • In my timezone its time to go home. Tomorrow I will try `BackgroundWorker` and hope to accept your answer. – Roland Mar 11 '15 at 15:58