~ Ready? ~
Cancellation is a common requirement for background tasks, especially when those tasks are consuming resources (like the CPU). In this round, we’ll be looking at the built-in cancellation support offered by
For our example, the background operation is just to sleep 100 times, 100ms at a time, for a total of 10 seconds. A second button is used to cancel the operation.
~ Fight! ~
BackgroundWorker has its own, unique way of doing cancellation. First, when constructing the BGW instance, be sure to set
true. Then, the calling code can request the worker to cancel by calling
DoWork handler should periodically check
CancellationPending, and when it detects cancellation, it should set
true. The calling code can check whether cancellation happened by reading
Here’s the code:
One of the biggest drawbacks to cancellation support in
BackgroundWorker is that it’s just plain convoluted. Even when I was using
BackgroundWorker regularly, every time I had to support cancellation, I had to look up how to do it. It’s just too complex to remember easily.
Another minor drawback is how cancellation is observed in
RunWorkerCompleted; it can be easy to overlook the fact that the operation was cancelled.
BackgroundWorker had a similar problem with error handling.
Task.Run uses the same cooperative cancellation model used by the rest of the .NET 4.0 framework. We note once again that
BackgroundWorker was passed over when other types were updated to use
CancellationToken - maybe that should tell us something…
Task.Run uses the same cancellation support as every other modern API, it’s much easier to remember. Also, it’s easier to implement:
Compared to the
BackgroundWorker code, the
Task.Run code has fewer moving pieces, so it’s simpler and shorter. Another benefit of the
Task.Run approach is that it treats cancellation as an exceptional condition. This means you can’t ignore it accidentally and continue on thinking that the operation completed. It also allows you to propagate cancellation very naturally.
There’s yet another advantage to the
Task.Run approach, that is rather subtle. A lot of thought went into the
CancellationToken design, and it shows. The operation that supports cancellation only knows about the
CancellationToken; it doesn’t need to know its own
Task or even the
CancellationTokenSource; all it has is the token, which only allows it to react to cancellation. This is a much cleaner design than the
BackgroundWorker approach, where the
DoWork handler has to interact with its own
Task.Run wins this round. Should that really be a surprise at this point? This time, the benefits of
Task.Run are shorter code, simpler code, using common types, and a well-designed API that encourages separation of concerns.