22 June 2010

Fire & Forget BackgroundWorker.RunAsync()

The BackgroundWorker is an extremely handy threading tool.  However, stopping and then immediately starting the BackgroundWorker isn't something built in to the class.  The problem is that you can not call BackgroundWorker.RunAsync() when the worker is already busy (.IsBusy) or is busy and pending cancellation (.CancellationPending).  You must wait until the worker is no longer busy.

Simply use a timer, to periodically check the status of the worker.  This code is able to handle any number of background workers.  This approach has been very helpful, when building services that monitor and maintain other processes.  Remember, IsBusy returns true until the thread terminates; therefore, when CancellationPending returns true, we know IsBusy will also returns true.

If you find yourself in this situation of handling mutually exclusive BackgroundWorkers or need to restart a background worker, chances are you have over-complicated your code and need to re-engineer your architecture. I strongly suggest you do that, before implementing this solution. The other, more remote possibility is you have a very unique situation that requires advanced coding.


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Timers;

BackgroundWorker MyWorker;
List<backgroundworker> WorkersToStart;
System.Timers.Timer timStartWorkerTimer;

void Main(string[] args) {
    timStartWorkerTimer = new System.Timers.Timer(1000);
    timStartWorkerTimer.Enabled = false;
    timStartWorkerTimer.Interval = 1000;
    timStartWorkerTimer.Elapsed += 
        new ElapsedEventHandler(timStartWorkerTimer_Elapsed);

    MyWorker = new BackgroundWorker();
    MyWorker.WorkerSupportsCancellation = true;
}

private void StartBackgroundWorker(
        ref BackgroundWorker bgw, bool cancelIfRunning) {
    // When bgw.CancellationPending is true,
    // bgw.IsBusy is also true.
    if (!bgw.IsBusy)    
        bgw.RunWorkerAsync();
    else if (cancelIfRunning) {
        bgw.CancelAsync();
        WorkersToStart.Add(bgw);
        timStartWorkerTimer.Start();
    } // Else, do nothing. The worker is already
      // started and cancelIfRunning == false.
}

void timStartWorkerTimer_Elapsed(
        object sender, ElapsedEventArgs e) {
    foreach (BackgroundWorker bgw in WorkersToStart)
        if (!bgw.IsBusy) bgw.RunWorkerAsync();

    if (WorkersToStart.Count == 0)
        timStartWorkerTimer.Stop();
}

No comments:

Post a Comment

Please provide details, when posting technical comments. If you find an error in sample code or have found bad information/misinformation in a post, please e-mail me details, so I can make corrections as quickly as possible.