Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WPF

Fancy Busy Handling with IDisposable and IProgress

5.00/5 (4 votes)
23 Oct 2014CPOL4 min read 8.9K   157  
This tip describes how to handle BusyIndication and progress report in an MVVM project for several scenarios.

Introduction

This tip shows how to simplify your code for progress reports and busy indication in a MVVM project.

The first part is about handling busy indication for multiple concurrent tasks.The second extends the solution to support progress reports. At the end, there will be some code to report status from LINQ-Queries.5

Background

In my current project, multiple concurrent actions activate the BusyIndicator. Therefore a simple bool was not enough and a more powerful implementation was needed. In this scenario, I ran into the problem, that each action had to be wrapped in a try-finally-block or to ensure, that the busy indication gets stopped correctly. In the end, there was a lot of overhead in the code just for ensuring that the BusyIndicator works correctly.

After finding a solution for this, I decided to add progress report using the same pattern as for normal busy indication. There were also some LINQ-Queries that took a while to process. I also wanted them to report the current progress. So I came up with some ideas that solve the problems in my scenario.

Using the Code

First let's take a look at the BusyHandler that manages multiple concurrent progresses. I decided to create a method "Activate()" that returns an IDisposable. This has the advantage, that the code for doing the work can be encapsulated in an using-block. The BusyHandler has a nested class, that implements the IDisposable interface. In the constructor, an internal counter of the BusyHandler gets incremented and Dispose decrements the counter in the end. The BusyHandler has a property "IsBusy" which notifies if the status changes and can be bound directly in XAML. Using the BusyIndicator is straight forward:

C#
//Activates the BusyIndicator, waits for 3 seconds and deactivates it in the end
private async void DoSomething()
{
    using (BusyHandler.Activate())
    {
        await Task.Delay(3000);
    }
}

As you can see, the usage of it is really simple and the busy progress gets stopped in any case. Now, it is possible to handle exceptions in a different place, without catching them, deactivating the busy progress and rethrowing them again.

The next step is to report progress. For this, the .NET-Framework has the interface IProgress<T>. I have created a new interface that implements IProgress<T> and IDisposable to support progress report in the same fashion as the simple busy indication. For this scenario, a counter is not enough, so I decided to create a dictionary that holds the status of the progress for all of the currently running tasks. A second nested class implements my new IDisposableProgress interface. In the constructor, it registers its progress in the internal Dictionary of the BusyHandler. The "Report"-method from the IProgress-interface updates the current status of the progress and the dispose method marks the item as finished. The BusyHandler has a property "Progress" which sums up the individual values and divides it by the number of items in the dictionary.

Removing the entries of a progress in the dictionary in the dispose method made the ProgressBar jump back to a lower value when a progress is finished and others are still running. The solution for getting a normal behavior is to remove the items only when all of them are finished or when another item is added.

Now, we have a BusyHandler that is easy to use and that supports progress reports:

C#
//Activates the BusyIndicator reports the Progress every 10ms
private async void DoSomethingWithProgressReport()
{
    using (var busyHandler = BusyHandler.ActivateWithProgress())
    {
        for (int i = 0; i < 1000; i++)
        {
            busyHandler.Report(i/10.0);
            await Task.Delay(10);
        }
    }
}

After creating the infrastructure for progress reports, let's enable LINQ to report progress. I assume you know that LINQ works with ExtensionMethods that are set on top of the IEnumerable interface. The only thing we have to do to get a progress report is to create another ExtensionMethod that reports the progress while enumerating the source collection. The following snippet shows how that can be done:

C#
//Creates an enumerable with progress report
public static IEnumerable<T> WithProgressReport<T>
(this IEnumerable<T> source, IProgress<double> progressHandler,
    int itemCount, double reportInterval)
{
    var reportingCountInterval = (int)(itemCount / (100/reportInterval));
    var counter = 0;
    foreach (var item in source)
    {
        yield return item;
        counter++;
        if (counter % reportingCountInterval == 0)
        {
            progressHandler.Report(counter * 100.0 / itemCount);
        }
    }
}

As you can see, we need the number of items and the interval for sending the progress report. After processing an item, we update the progress if needed. If the source collection is an array or an ICollection<T>, the number of items is known. With this, we can create overloads which are much nicer to use. For example:

C#
public static IEnumerable<T> WithProgressReport<T>(this ICollection<T>source, 
    IProgress<double> progressHandler,double reportInterval = 0.1)
{
    return source.WithProgressReport(progressHandler, source.Count, reportInterval);
}

Using it is again quite simple:

C#
private async void ActicateBusyWithProgressWithLinq()
{
    using (var busyHandler = BusyHandler.ActivateWithProgress())
    {
        await Task.Run(() =>
        {
            var result =
                Enumerable.Range(0, 1000)
                    .ToList()
                    .WithProgressReport(busyHandler)
                    .Select(DoHeavyProcessing)
                    .ToList();
        });
    }
}

Now we have a BusyHandler that supports busy indication and progress report and we have an Extension to enable progress report within LINQ-queries. For me in the beginning, it all looked nice and smart but there are still some issues left. First of all, the progress report for LINQ only works for one iteration of the IEnumerable. If you iterate the IEnumerable that is returned from "WithProgressReport", the progress gets reported from the beginning over and over again. So if you use this implementation, please enumerate the IEnumerables only once after using "ReportWithProgress". My second concern is that I'm pretty sure that IDisposable was not made for using it like this, but until now for me it works fine and removed a lot of boilerplate code.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)