简体   繁体   中英

Indeterminate ProgressBar animation jerk in Dispatcher.BeginInvoke

I have a piece of code testing the GUI and threading behavior. I want to keep ProgressBar animation running (with IsIndeterminate="True" ) as I query the database and add a large number of rows (10K+) into the DataGrid. Even if I wrap the database and GUI code in Dispatcher.BeginInvoke , the ProgressBar animation would jerk as the DataGrid is being filled.

I would expect the ProgressBar animation would either freeze (if on GUI thread) or run smoothly (if on a separately thread), but I cannot understand why the animation is running jerkingly.

Please do not suggest BackgroundWorker , as I want to understand the problem in this question, and why BeginInvoke is not separating the threads. I simply loop through SqlDataReader and add to DataGrid as Item one by one instead of databinding to a source or a datatable.

// XAML
<Button Click="Button_Click"></Button>
<ProgressBar IsIndeterminate="True"></ProgressBar>
<DataGrid ... ></DataGrid>

// C#
private void Button_Click(object sendoer, RoutedEventArgs e)
{
    this.Dispatcher.BeginInvoke(DispatcherPriority.Normal,(ThreadStart)delegate()
    {
        // Query database and update GUI (e.g. DataGrid)
    });
}

The Dispatcher always executes code on the thread it is associated with (the UI thread in your case), regardless of whether you use Invoke or InvokeAsync (which is a convenient shorthand for BeginInvoke ). So all the work regarding loading data from database and updating the DataGrid is done on UI thread, hence the animation is not smooth.

The difference between Invoke and InvokeAsync is that the former is executed synchronously , and the latter is executed asynchronously . What it means is that in the first case the calling thread will be suspended until the delegate has finished executing (ie it will be synchronized ), whereas in the second case the thread will continue its execution without waiting for the delegate to finish. Let me try to point out this difference using examples.

Example I. The methods are called from the UI thread (as in your case)

Let's assume we only have one thread (the UI thread). Calling Invoke will not have any noticeable effect, since the delegate will be executed immediately and only then the execution will continue. So this:

Dispatcher.Invoke(() => DoSomeStuff());
DoSomeOtherStuff();

will have the same effect as this:

DoSomeStuff();
DoSomeOtherStuff();

Calling BeginInvoke however will have an effect such that the delegate will be scheduled to execute only after all scheduled tasks with higher priority (or already scheduled with the same priority) are executed. So in this case:

Dispatcher.InvokeAsync(() => DoSomeStuff());
DoSomeOtherStuff();

DoSomeOtherStuff() will be executed first, and DoSomeStuff() second. This is often used for example in event handlers where you need some code to be executed only after the event is completely handled (eg see this question ).

Example II. The methods are called from a different thread

Let's assume we have two threads - the UI thread, and a worker thread. If we call Invoke from the worker thread:

Dispatcher.Invoke(() => DoSomeStuff());
DoSomeOtherStuff();

first DoSomeStuff() will be executed on UI thread, and then DoSomeOtherStuff() will be executed on worker thread. In case of InvokeAsync :

Dispatcher.InvokeAsync(() => DoSomeStuff());
DoSomeOtherStuff();

we only know that DoSomeStuff() will be executed on UI thread and DoSomeOtherStuff() will be executed on worker thread, but the order in which they will be executed is indeterminate*.

Usually Invoke is used when your delegate yields some result and you need it to continue execution on the worker thread (for example when you need to obtain a dependency property value). InvokeAsync on the other hand is usually used when the delegate does not yield any result (or the result is ignored), such as in your case - updating DataGrid does not yield any result worth waiting for so you can immediately continue to load the next batch of data.

I hope that sheds some light on the issue for you and you can see why the solution to "jerky UI" is to delegate heavy work to another thread and only use dispatcher to interact with UI. That's were suggestions to use BackgroundWorker or Task come from.

*Actually they probably will be executed simultaneously. What I meant was if for example both methods only print some text to console, the order of messages in the console is indeterminate.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM