3

What is the difference between options 1 and 2 in the following?

    private void BGW_DoWork(object sender, DoWorkEventArgs e)
    {
        for (int i=1; i<=100; i++)
        {
            string txt = i.ToString();
            if (Test_Check.Checked)
                //OPTION 1
                Test_BackgroundWorker.ReportProgress(i, txt); 
            else
                //OPTION 2
                this.BeginInvoke((Action<int, string>)UpdateGUI, 
                                  new object[] {i, txt});
        }
    }

    private void BGW_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        UpdateGUI(e.ProgressPercentage, (string)e.UserState);
    }

    private void UpdateGUI(int percent, string txt)
    {
        Test_ProgressBar.Value = percent;
        Test_RichTextBox.AppendText(txt + Environment.NewLine);
    }

Looking at reflector, the Control.BeginInvoke() appears to use:

this.FindMarshalingControl().MarshaledInvoke(this, method, args, 1);

Which seems to eventually call some native functions like PostMessage(), couldn't exactly figure out the flow from reflector (pesky compiler goto optimizations)

Whereas BackgroundWorker.Invoke() appears to use:

this.asyncOperation.Post(this.progressReporter, args);

Which seems to eventually call ThreadPool.QueueUserWorkItem()

(I'm just guessing these are the relevant function calls for each case.) If I understand correctly, using the ThreadPool would not guarantee execution order whereas using the Post mechanism would. Perhaps that would be a potential difference ? (EDIT - I couldn't synthesize such a situation - call order seems to be preserved in both cases, at least in my simple tests.)

Thanks!

Ohad Schneider
  • 36,600
  • 15
  • 168
  • 198

3 Answers3

2

One big difference is that Control.Invoke will block until the UpdateGUI call has been executed and completed, whereas BackgroundWorker.ReportProgress will not block (it will return immediately, before the BackgroundWorker raises the event).

If you want them to behave the same, call Control.BeginInvoke (which doesn't block) instead.

MusiGenesis
  • 74,184
  • 40
  • 190
  • 334
  • Thanks, but it was not my intention - I just revised the question – Ohad Schneider May 19 '10 at 13:51
  • My answer's the same. ReportProgress is asynchronous, Control.Invoke is synchronous. In the case of a progress bar, choosing one or the other would probably have no noticeable effect. If you use ReportProgress, the GUI update will be nearly instantaneous anyway - the only reason it wouldn't be is if your form is busy processing a bunch of other events at the same time. – MusiGenesis May 19 '10 at 14:04
  • Yes but I am now asking about the difference between ReportProgress and *BeginInvoke* – Ohad Schneider May 19 '10 at 14:06
  • The difference would be much more significant if your UpdateGUI method did a bunch of time-consuming work. Then your choice would be between running the background process at maximum speed but with delayed GUI updates (ReportProgress) or getting real-time GUI updates but slowing down the background process (Control.Invoke). – MusiGenesis May 19 '10 at 14:07
  • Oh, sorry, I didn't notice the `BeginInvoke` change. Forget what I said in the comments - you appear to be aware of that stuff already. :) – MusiGenesis May 19 '10 at 14:09
  • What does Reflector show when you change to BeginInvoke? I wouldn't be surprised if it results in the same code as ReportProgress. – MusiGenesis May 19 '10 at 14:13
  • I've included my (limited) reflector findings below the code :) – Ohad Schneider May 19 '10 at 14:19
2

They are both the same. The call you're seeing in BackgroundWorker uses SynchronizationContext. Indeed the default implementation of the Post() method uses the thread pool, but when starting a Windows Forms app, the default synchronization context is replaced by WindowsFormsSynchronizationContext, which actually calls Control.BeginInvoke().

Eli Arbel
  • 22,391
  • 3
  • 45
  • 71
0

I've found a significant difference. Closing the form while the BGW is running will cause this.Invoke() and this.BeginInvoke() to throw an ObjectDisposedException. The BGW ReportProgress mechanism circumvents that. To enjoy the best of both worlds, the following pattern works nicely

public partial class MyForm : Form
{
    private void InvokeViaBgw(Action action)
    {
        Packing_Worker.ReportProgress(0, action);
    }

    private void BGW_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        if (this.IsDisposed) return; //You are on the UI thread now, so no race condition

        var action = (Action)e.UserState;
        action();
    }

    private private void BGW_DoWork(object sender, DoWorkEventArgs e)
    {
       //Sample usage:
       this.InvokeViaBgw(() => MyTextBox.Text = "Foo");
    }
}
Ohad Schneider
  • 36,600
  • 15
  • 168
  • 198