-1

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)
    });
}
KMC
  • 19,548
  • 58
  • 164
  • 253
  • The progress bar animation runs on the UI thread. WinRT is able to run animations on a different thread, but unfortunately this is still unavailable for WPF. As to know why it's running jerkingly, how are you binding the data to the DataGrid? – Kevin Gosse Jun 07 '17 at 06:44
  • I simply loop through SqlDataReader and add to DataGrid as Item one by one instead of databinding to a source or a datatable. – KMC Jun 07 '17 at 07:13
  • 1
    In your code example you do all work on UI thread (with BeginInvoke), so why would it be smooth? – Evk Jun 07 '17 at 07:38
  • Maybe I get this wrong - but it seems pretty obvious for me that running stuff on your dispatcher thread causes the UI to `jerk`. Can't you just query and update your `Database` async in the Dispatcher-Thread? – Peter Jun 07 '17 at 08:10

1 Answers1

3

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 (i.e. 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 (e.g. 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.

Grx70
  • 10,041
  • 1
  • 40
  • 55
  • '@Grx70' clearly explained, thank you. Since the application started on UIThread, everything affiliated to or owned by UIThread (e.g. new Thread() in Main() ). So 'Dispatcher' does not create a thread, but its function is to simply prioritize different works synchronously or asynchronously on the same thread or allow a UI-owned thread to access UIThread. Is it also the case for 'BackgroundWorker' then? Because if I call Dispatcher.BeginInvoke on BGW's DoWork to update UIThread I would still encounter threading exception. Would you help clarify this understanding? – KMC Jun 07 '17 at 08:55
  • Yes, dispatcher should also be safe choice to use together with `BackgroundWorker`. If you're having issues with that it's best if you asked new question and provide a [MCVE](https://stackoverflow.com/help/mcve). – Grx70 Jun 07 '17 at 09:03
  • [link] (https://stackoverflow.com/questions/44409234/backgroundworker-to-read-database-and-update-gui) – KMC Jun 07 '17 at 09:57