I recently received a question from a person we will refer to as “Hingle McCringleberry”, that was related to an old post I did back in 2011 showing how to use the Extended WPF Toolkit’s BusyIndicator control, back when I was the project owner.  In that post, I showed how to use the BackgroundWorker to create a multi threaded application and report progress back to the UI.  Of course it worked like a charm.  Well, a lot of things have changed since then, and there are new APIs available to perform multithreading in WPF applications.  So, Hingle McCringleberry decided to use these new APIs, specifically the async/await APIs, to modify the old sample.  Should be straight forward right?  Well, yes and no.

The problem was that even though the multithreading code was working perfectly, the UI would freeze during the process, although it should remain responsive.  I mean, that’s why we do multithreading in the first place, right.  So let’s takes a look at the sample and see if we can identify the problem.

Here is the view:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition />
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <ListBox x:Name="_listBox" />
        <Button x:Name="_button" Grid.Row="1" Click="StartProcess">Start Process</Button>
    </Grid>

    <Grid Grid.Row="1" Margin="15">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <ProgressBar x:Name="_progressBar" Width="200" Height="20" Minimum="0" Maximum="1" Margin="20" />
        <TextBlock x:Name="_progressText" Grid.Row="1" FontSize="48" FontWeight="Bold" HorizontalAlignment="Center">0%</TextBlock>
    </Grid>
</Grid>

Here is the code behind:

private async void StartProcess(object sender, RoutedEventArgs e)
{
    var listOfStrings = await GenerateItems();

    _listBox.ItemsSource = listOfStrings;
}

Task<List<String>> GenerateItems()
{
    IProgress<double> progress = new Progress<double>(UpdateProgressText);

    return Task.Run(() =>
    {
        int maxRecords = 1000000;

        List<String> listOfStrings = new List<string>();
        for (int i = 0; i < maxRecords; i++)
        {
            double percentage = (double)i / maxRecords;
            progress.Report(percentage);

            listOfStrings.Add(String.Format("Item: {0}", i));
        }

        return listOfStrings;
    });
}

public void UpdateProgressText(double percentage)
{
    _progressBar.Value = percentage;
    _progressText.Text = (percentage).ToString("0%");
}

At first glance everything appears like it should work just fine.  When we click the “Start Process” button, we generate 1,000,000 items, add them to a list, and then set the ItemsSource of the ListBox to the items we generated.  As you can see, we are using the IProgress<T> which makes it extremely easy to report progress to the UI.  So as items are created, we are calculating the percentage done, and updating a TextBlock and a ProgressBar to our end-user so they know how much longer they have until the process is complete.  But, if you run the app as it is, you will notice that when you click the “Start Process” button, the UI freezes until the operation is completed.

task-run-problem

When everything is done, the the ProgressBar and TextBlock suddenly go to 100%.  This is not what we want.  We need the UI to remain responsive, and increment the values so that the end-user can see the actual progress of the operation.  So what’s going on here?

You’re flooding your UI with too many updates!

That’s right, you are pushing way too many update to the UI.  “Hingle McCringleberry” didn’t realize he was pushing 1,000,000 individual updates to the UI within milliseconds.   So fast, that the UI didn’t have time to repaint.  Since messages that say “go update this” have a higher priority that a render/paint message, it never gets around to re-rendering /repainting the UI.

So how do we fix it?  Easy!  You just have to control how many updates you are sending.  I mean, you don’t have to update the UI every singlle time.  You just need to give your end-user a really close estimate of completion.  So just add a simple modification to your code.

if (i % 1500 == 0)
{
    double percentage = (double)i / maxRecords;
    progress.Report(percentage);
}

In this case, we said I will update the UI every 1500 items being generated.  The result is night and day.  Check it out:

task-run-fixed

Now, that’s much better!

That wraps up this post.  Be sure to check out the source code, and start playing with it.  As always, feel free contact me on my blog, connect with me on Twitter (@brianlagunas), or leave a comment below for any questions or comments you may have.

Brian Lagunas

View all posts

6 comments

  • While I agree that throttling on the background thread is the correct approach, it’s often not clear how much throttling to do. 1500 works today, but what about when the background code changes? What about vastly different CPU capabilities, or – if the background work does I/O – SSDs or unexpectedly fast networks?

    To be clear, I’m speaking from experience; I’ve had to tweak these kinds of throttling parameters in the past. And it really brings to the forefront that this is the wrong place to solve the problem: this solution is adding throttling *in the background work* in order to solve a *UI* update issue *on this particular hardware*.

    I’ve gone through a few iterations of dealing with this, and ended up writing my own IProgress implementation based on Rx, available at this gist: https://gist.github.com/StephenCleary/4248e50b4cb52b933c0d

    The idea is fairly simple: do *time*-based (not *count*-based) throttling on the background thread, but do it in the IProgress implementation. So the background logic doesn’t have to worry about the UI at all; it’s the UI responsibility’s to apply the throttling.

    • Wow, that’s awesome! Yet, simple. Do you have any samples, or blog post, using your ObservableProgress in action? I would love to play with it. I would much rather use that than have to add logic to my progress reporting logic for every operation.

      EDIT: Never mind, I was too quick to comment. All I had to do was scroll down a little more to see the sample. LOL

    • So I tried to use your ObservableProgress using the sample in this post, and even with the interval set to 1 ms, the progress never reports to 100%. It will stop at 99%, and as you increase the interval, the last percentage completed to get rendered to the UI gets lower. I am assuming this is because it is timed base, and not item based. So the very last percent isn’t rendered because the operation finishes before the final timed update.

      using (var progress = ObservableProgress.CreateForUi(new TimeSpan(0,0,0,0,1), UpdateProgressText))
      {
      var listOfStrings = await GenerateItems(progress);

      _listBox.ItemsSource = listOfStrings;
      }

      EDIT: After thinking about it, this may not be such a big deal because in most situations when you are at 99%, the progress indicator in the UI would be hidden since the operation would have completed immediately after you hit 99%.

  • I don’t quite understand why the sample works. We’re updating the UI from within a task (which is not on the UI thread). Am I missing something?

    • I am assuming you are expecting some type of Dispatcher call within the Task. Well, you don’t need it. IProgress automatically marshals the code from the Task.Run thread back up to the UI for you. Magic :0)

Follow Me

Follow me on Twitter, subscribe to my YouTube channel, and watch me stream live on Twitch.