vcmkrtchyan
vcmkrtchyan

Reputation: 2626

Where to call Control.Invoke/BeginInvoke in multi-thread operation?

I was writing a sample using delegates and updating UI from other threads, and came across this question.
Here's my sample code;

public partial class Form1 : Form
{
    private decimal _sum;
    private delegate void SetSum();
    private readonly SetSum _setSum;
    public Form1()
    {
        InitializeComponent();
        _setSum = () =>
        {
            txtSum.Text = _sum.ToString(CultureInfo.InvariantCulture);
        };
    }
    private void btnLoad_Click(object sender, EventArgs e)
    {
        new Thread(Method).Start();
    }
    private void Method()
    {
        for (decimal i = 0; i < 100000000; i++)
        {
            _sum += i;
        }
        //txtSum.BeginInvoke(_setSum);
        this.BeginInvoke(_setSum);
    }
}

What is the difference if I call the BeginInvoke method on this and txtSum? Both are controls from the UI thread, and both give no exception invoking my delegate and to their job great, so how to decide what control to pick for calling the delegate?

Upvotes: 3

Views: 740

Answers (2)

Fabjan
Fabjan

Reputation: 13676

The method Control.BeginInvoke() is executed on a thread that the control's underlying handle was created on... In your case it should make no difference.

According to MSDN :

The delegate is called asynchronously, and this method returns immediately. You can call this method from any thread, even the thread that owns the control's handle. If the control's handle does not exist yet, this method searches up the control's parent chain until it finds a control or form that does have a window handle. If no appropriate handle can be found, BeginInvoke will throw an exception.

Most methods on a control can only be called from the thread where the control was created. In addition to the InvokeRequired property, there are four methods on a control that are thread safe: Invoke, BeginInvoke, EndInvoke, and CreateGraphics if the handle for the control has already been created

More info from MSDN: https://msdn.microsoft.com/en-us/library/0b1bf3y3%28v=vs.110%29.aspx

Think of this scenario:

public partial class Form1 : Form
{
    Control c;

    public Form1()
    {
        Task.Factory.StartNew(() => { c = new Control(); });          
    }
}

If you use this.BeginInvoke(someMethod) it's possible that it throws an exception when you try to invoke method of a Control from a different thread than the one where it was created (from UI thread in our case). So that's when it might be better use c.BeginInvoke(someMethod)...

Upvotes: 3

Hans Passant
Hans Passant

Reputation: 942438

You need to specify a control to Begin/Invoke() to because Winforms needs to figure out which specific thread needs to execute the delegate target. It will be the thread that created the Handle property. Underlying winapi call is GetWindowThreadProcessId. Otherwise the reason it will spit bullets when you call Begin/Invoke() to early or too late.

The choice is superseded by another rock-hard rule, the child controls of a toplevel window must be created on the same thread as the window. Kaboom if it wasn't.

So it just doesn't matter, you have a hard guarantee that the TextBox and the Form both will marshal to the exact same thread.

But it is better not to have to make the choice at all. You are always fighting a threading race bug when you do, nothing pretty ever happens when the thread survives a bit longer than the window. Pretty inevitable because the lifetime of the window and its controls is not directly under your control. It is the user that decides when they get disposed. But it is you that controls the thread. The BackgroundWorker and Task classes make it a lot easier to reason about this.

Upvotes: 1

Related Questions