~ Ready? ~
When you have a lengthly background operation, it’s considered polite to give some kind of progress notification to the user (if possible). This usually takes the form of a percentage, with some optional extra information (e.g., the name of the current file being processed).
We’ll take the same operation as last time (sleep for 100ms 100 times, for a total of 10 seconds), and this time we’ll report progress at each iteration. Since we’ll be updating the UI with our progress information, we want each progress update to be raised in the UI context.
~ Fight! ~
BackgroundWorker has built-in support for progress reporting, and even automatically marshals to the UI thread. Like last time, we have to first set the
BackgroundWorker.WorkerSupportsProgress property to allow progress reports. Then, the
DoWork method can call the
BackgroundWorker.ReportProgress method, which raises the
Just like last time, this approach works but is rather convoluted. Again, we see the
BackgroundWorker type having too many responsibilities: it has the enabling property, the method to report progress, and the event that is raised when the progress changes.
async support also introduces a pattern for progress reporting in asynchronous methods: the caller optionally creates an implementation of
IProgress<T> (usually an instance of
Progress<T>), and that instance is passed into the asynchronous method. The method then sends progress reports to its
IProgress<T> instance (if it is not
What we end up here is also somewhat convoluted. Normally, the
IProgress<T> is a method parameter, but for these examples I’m trying to keep everything in the same method. Also, in this example there’s no need to check
progress != null, but that is standard practice for asynchronous code so I’m including it here.
OK, so the
Task.Run code is not a lot better than
BackgroundWorker. It is better, though; it’s a bit shorter, and the types definitely have better separation of concerns. So far, I’d way it’s winning by technicalities instead of by a knockout.
Let’s take the stakes a bit higher. Let’s say we want to report strings instead of an integer percentage. Furthermore, let’s pretend we’re doing an operation where the percent complete is difficult to compute, so instead we’ll report strings that describe the current stage of the operation.
~ Fight! ~
BackgroundWorker.ReportProgress takes an optional second argument, which is a custom “progress report” instance. This is then available to the progress changed handler as
There are a couple of drawbacks to the way
BackgroundWorker reports custom progress types. The first is that it is untyped (we just get an
object instance, which we cast to
string). The second is that we must pass back a “percent complete”, even if there is no way to calculate a meaningful value for that parameter. You can, of course, just pass zero and document that the caller should ignore that value.
Progress<T> types allow any type for
T, so changing the progress report type from
string is quite straightforward:
I must conclude again that this round goes to
Progress<T> types are strongly typed, have better separation of concerns, and they easily allow any type of custom progress report.