2

Given the C# code example:

using System;
using System.Threading;
using System.Windows.Forms;

public class MnFrm : Form
{
   private void MnFrm_Load(Object sender, EventArgs e)
   {
      this.WorkCompleted += MnFrm_WorkCompleted;
   }

   private void btn_Click(Object sender, EventArgs e)
   {
      ThreadPool.QueueUserWorkItem(AsyncMethod);
   }

   private void MnFrm_WorkCompleted(Object sender, Boolean e)
   {
      MessageBox.Show("Work completed");
   }

   private void AsyncMethod(Object state)
   {
      // Do stuff
      Boolean result = true; // just as an example
      WorkCompleted?.Invoke(this, result);
   }

   private event EventHandler<Boolean> WorkCompleted;
}

When the user clicks the button btn the method AsyncMethod is executed on another thread managed by the ThreadPool. After some time the work is done and the result is posted back via another event. This event handler (WorkCompleted) executes on the thread used to run AsyncMethod because when the app is executed you get the 'Cross-Threading' exception.

So the question is how to run the event handler MnFrm_WorkCompleted on the UI thread?

Henk Holterman
  • 236,989
  • 28
  • 287
  • 464
  • Correct solution will depend on what `AsyncMethod` is actually doing. If method accessing some external resources then your code could be changed to effectively run on one thread without worries about threads/invokes and other multi-threading related problems. – Fabio Oct 06 '17 at 07:31
  • 1
    Why are you using `QueueUserWorkItem` instead of `var result=await Task.Run(...)` ? It's available in all supported .NET versions and converts all of this code into a *single* line that handles asynchronous operations correctly. The earliest supported .NET version is 4.5.2. `await` was added earlier, in 4.5. Tasks in 4.0 – Panagiotis Kanavos Oct 06 '17 at 07:37
  • 2
    Even if you need to report something from another thread, don't use events. That's the job of the IProgress interface and the `Progresss` class – Panagiotis Kanavos Oct 06 '17 at 07:38

3 Answers3

2

You can use Control.Invoke Or Control.BeginInvoke methods to invoke particular method on UI thread. Try the below code:

private void MnFrm_WorkCompleted(Object sender, Boolean e)
{
    if (InvokeRequired)
    {
       Invoke((Action) (() => MnFrm_WorkCompleted(sender, e)));
       return;
    }
    MessageBox.Show("Work completed");
}

For difference between Invoke And BeginInvoke : What's the difference between Invoke() and BeginInvoke()

iamyz
  • 62
  • 1
  • 12
0

If you change it to this:

this.Invoke(new Action(() =>
{
    WorkCompleted?.Invoke(this, result);
});

It will work. Thats because the Form contains a method Invoke() which will invoke the method on the thread it was created on. The Invoke of an event is nothing more than calling the delegate.

Read here form moet info.

You could use the InvokeRequired to determine if you're on the right thread, but I think it's overhead and makes it less readable. Always use this.Invoke.


BeginInvoke, This is usefull when 'posting' a message to the UI thread. The only problem is, when the thread is raising many events and the UI thread doesn't have enough time to process them, you can't see how many are queued.


A third method is using a queue. When the event happens, add a message to a queue (I would use a List<>) and use a form timer to process the queue. The advantage is that the thread isn't stalled by the UI thread and continues directly after adding it to the queue. And your application will be stalled.

Jeroen van Langen
  • 18,289
  • 3
  • 33
  • 50
0

Thanks all!

The answer from Jeroen van Langen is quite correct! It worked. It was actually how I did it but did not want to post the solution in order to prevent bias - I wanted to see alternative solutions.

However, I prefer the answer from Panagiotis Kanavos where he suggested using var result=await Task.Run(...). That cleaned the code beautifully! Thank you Panagiotis Kanavos!!!