Dispatcher Invoke(...) vs BeginInvoke(...) confusion
Asked Answered
H

1

43

I'm confused why I can't make this test counter application work with 2 (or more) simultaneous running countertextboxes with the use of "BeginInvoke" on my Dispatcher in the Count() method.

You can solve the issue by replacing the BeginInvoke by an Invoke. But this doesn't solve my confusion.

Here's the sample code I'm talking about:

public class CounterTextBox : TextBox
{
    private int _number;

    public void Start()
    {
        (new Action(Count)).BeginInvoke(null, null);
    }

    private void Count()
    {
        while (true)
        {
            if (_number++ > 10000) _number = 0;
            this.Dispatcher.BeginInvoke(new Action(UpdateText), System.Windows.Threading.DispatcherPriority.Background, null);    
        }
    }

    private void UpdateText()
    {
        this.Text = "" + _number;
    }
}
Haun answered 25/9, 2013 at 15:33 Comment(0)
I
110

When you use Dispatcher.BeginInvoke it means that it schedules the given action for execution in the UI thread at a later point in time, and then returns control to allow the current thread to continue executing. Invoke blocks the caller until the scheduled action finishes.

When you use BeginInvoke your loop is going to run super fast since BeginInvoke returns right away. This means that you're adding lot and lots of actions to the message queue. You're adding them much faster than they can actually be processed. This means that there's a long time between when you schedule a message and when it actually gets a chance to be run.

The actual action that you're running uses the field _number. But _number is being modified by the other thread very quickly and while the action is in the queue. This means that it won't display the value of _number at the time you scheduled the action, but rather what it is after it has been continuing on in it's very tight loop.

If you use Dispatcher.Invoke instead then it prevents the loop from "getting ahead of itself" and having multiple scheduled events, which ensures that the value that it's writing is always the "current" value. Additionally, by forcing each iteration of the loop to wait for the message to be run it makes the loop a lot less "tight", so it can't run as quickly in general.

If you want to use BeginInvoke the first thing you really need to do is slow down your loop. If you want it to update the text every second, or ever 10ms, or whatever, then you can use Thread.Sleep to wait the appropriate amount of time.

Next, you need to take a copy of _number before passing it to the Dispatcher so that it displays the value at the time you scheduled it, not at the time it is executed:

while (true)
{
    if (_number++ > 10000)
        _number = 0;
    int copy = _number;
    this.Dispatcher.BeginInvoke(new Action(() => UpdateText(copy))
        , System.Windows.Threading.DispatcherPriority.Background, null);
    Thread.Sleep(200);
}

private void UpdateText(int number)
{
    this.Text = number.ToString();
}
Inbeing answered 25/9, 2013 at 15:51 Comment(5)
Excellent description. +1 for reminding people to make a copy of variables being passed across the thread boundary.Enervate
@JesseChisholm This is not about passing a value across thread boundaries, it's about copying the value of a variable that is being closed over when you want the semantics of closing over a value rather than closing over a variable. Closures deferring execution of some code doesn't need to involve multiple threads, even though it happens to in this particular case.Inbeing
OK. Agreed. Something else to think about is that BeginInvoke sometimes needs a balancing EndInvoke. See: #1274776 for more details. Not in this case, as Control.BeginInvoke (And therefore Form.BeginInvoke) is documented as a special case that does not require an EndInvoke.Enervate
Dispatcher doesn't implement ISynchronizeInvoke. The method happens to have the same name as that method, but it is semantically noticeably different.Inbeing
Now i just understand the different of both Invoke and BeginInvoke with your explanation. Great explanation +1Church

© 2022 - 2024 — McMap. All rights reserved.