Search code examples
.netmultithreadingbackgroundworker

Cancelling a BackgroundWorker, how to prevent race when it has already finished


I'm using a BackgroundWorker to perform a long computation (only one such computation at a time).

The user has the possibility of cancelling the worker (calling worker.CancelAsync).

In the worker.DoWork method I periodically check for the cancel pending flag and then return from the method.

Then the Completed event is raised from the worker and I can check that the worker was cancelled. Furthermore, and that is the important thing, I do some extra cleanup when a cancel is detected.

I am sure there could be problem if the user cancels the worker and it's already returned from the DoWork method. In that case I would really like to know that the worker was cancelled so I could cleanup...

Is there a better way to handle the cancel procedure, with cleanup, of a worker ?


Solution

  • Your DoWork event handler shoud periodically check BackgroundWorker.CancellationPending, and set DoWorkEventArgs.Cancel to true before returning if it was cancelled.

    Your RunWorkerCompleted event handler should check the RunWorkerCompletedEventArgs.Cancelled property to determine if the DoWork event handler cancelled (set DoWorkEventArgs.Cancel to true).

    In the event of a race condition, it may happen that the user requested cancellation (BackgroundWorker.CancellationPending is true) but the worker didn't see it (RunWorkerCompletedEventArgs.Cancelled is false). You can test these two properties to determine that this has occurred, and do whatever you choose (either treat it as successful completion - because the worker did actually finish successfully, or as a cancellation - because the user has cancelled and doesn't care any more).

    I don't see any situation where there is any ambiguity about what happened.

    EDIT

    In response to the comment - if there are several classes that need to detect CancellationPending, there's no really no alternative to passing to these classes a reference to a type such as BackgroundWorker that allows them to retrieve this information. You can abstract this into an interface, which is what I generally do as described in this response to a question about BackgroundWorkers. But you still need to pass a reference to a type that implements this interface to your worker classes.

    If you want your worker classes to be able to set DoWorkEventArgs.Cancel you will need to either pass a reference to this around, or adopt a different convention (e.g. a boolean return value or custom exception) that allows your worker classes to indicate that cancellation has occurred.