Click here to Skip to main content
Click here to Skip to main content

Understanding BackgroundWorker and Encapsulating your own Thread Class

, 30 Mar 2011
Rate this:
Please Sign up or sign in to vote.
Understanding BackgroundWorker threads and how to encapsulate your own thread class.

Introduction

You may have come across this page if you were searching for any of the following:

  • BackgroundWorker events not firing
  • BackgroundWorker RunWorkerCompleted event not firing
  • BackgroundWorker threads frozen
  • Encapsulate thread class

Yesterday, my web page was launching several worker threads and waiting for them to return to amalgamate the results into a single data set to bind to a grid. Launching several worker threads and waiting for them to return is a common pattern. To nicely encapsulate the thread itself, I derived a class from BackgroundWorker. BackgroundWorker has many advantages such as using an event model, thread pool, and all the thread plumbing built right in. All you have to do is override OnDoWork and off you go. The RunWorkerCompleted event was used, in conjunction with a lock, to collect the data once the worker thread finished.

Everything looked good, but for some reason, the event never fired. The problem was that I had gotten myself in to a deadlock scenario. The expectation is that when the event fires, the delegate method will run in the context of the thread which fired it. If this were true, this would have allowed my synchronization logic to operate normally and not deadlock. The reality is that BackgroundWorker goes out of its way to run this event in the calling thread’s identity. It did this, so when using BackgroundWorker in conjunction with the UI, no invoke will be required (an exception will be thrown if a thread tries to touch the UI’s controls requiring you to check InvokeRequired).

When in doubt, use something like this to check the identity of the thread executing the code:

Trace.WriteLine(string.Format(“Thread id {0}”, 
      System.Threading.Thread.CurrentThread.ManagedThreadId));

Once I put the above trace in the code, I could clearly see that the identity of my main thread was identical to the thread identity in the RunWorkerCompleted event. Once the code tried to acquire the lock, it was all over.

So the solution in my case was not to use the RunWorkerCompleted event. I added an alternative event to my thread class and called that at the end of OnDoWork. The event executed in the context of the thread, as expected, and my synchronization logic worked fine. But I couldn’t help feeling it was a bit of a kludge and pondered whether I should be deriving from BackgroundWorker at all. However, what’s the alternative? There really aren’t other alternatives to BackgroundWorker built in to the framework, but it is easy to create your own. See below:

/// <span class="code-SummaryComment"><summary>
</span>/// Abstract base class which performs some work and stores it in a data property
/// <span class="code-SummaryComment"></summary>
</span>/// <span class="code-SummaryComment"><typeparam name="T">The type of data this thread will procure</typeparam>
</span>public abstract class ThreadBase<T>
{
#region Public methods
    /// <span class="code-SummaryComment"><summary>
</span>    /// Does the work asynchronously and fires the OnComplete event
    /// <span class="code-SummaryComment"></summary>
</span>    public void DoWorkAsync()
    {
        DoWorkAsync(null);
    }

    /// <span class="code-SummaryComment"><summary>
</span>    /// Does the work asynchronously and fires the OnComplete event
    /// <span class="code-SummaryComment"></summary>
</span>    /// <span class="code-SummaryComment"><param name="arguement">The arguement object</param>
</span>    public void DoWorkAsync(object arguement)
    {
        ThreadPool.QueueUserWorkItem(DoWorkHelper, arguement);
    }

    /// <span class="code-SummaryComment"><summary>
</span>    /// Does the work and populates the Data property
    /// <span class="code-SummaryComment"></summary>
</span>    public void DoWork()
    {
        DoWork(null);
    }

    /// <span class="code-SummaryComment"><summary>
</span>    /// Does the work and populates the Data property
    /// <span class="code-SummaryComment"></summary>
</span>    /// <span class="code-SummaryComment"><param name="arguement">The arguement object</param>
</span>    /// <span class="code-SummaryComment"><remarks>
</span>    /// Can be called to run syncronously, which doesn't fire the OnComplete event
    /// <span class="code-SummaryComment"></remarks>
</span>    public abstract void DoWork(object arguement);
#endregion

#region Private methods
    private void DoWorkHelper(object arguement)
    {
        DoWork(arguement);
        if (OnComplete != null)
            OnComplete.Invoke(this, Data);
    }
#endregion

#region Properties
    public T Data { get; protected set; }
#endregion

#region Events

    /// <span class="code-SummaryComment"><summary>
</span>    /// Delegate which is invoked when the thread has completed
    /// <span class="code-SummaryComment"></summary>
</span>    /// <span class="code-SummaryComment"><param name="thread">The thread.</param>
</span>    /// <span class="code-SummaryComment"><param name="data">The data.</param>
</span>    public delegate void ThreadComplete(ThreadBase<T> thread, T data);

    /// <span class="code-SummaryComment"><summary>
</span>    /// Occurs when the thread completes.
    /// <span class="code-SummaryComment"></summary>
</span>    /// <span class="code-SummaryComment"><remarks>This event operates in the context of the thread</remarks>
</span>    public event ThreadComplete OnComplete;
#endregion
}

My generic ThreadBase class is a lightweight base class substitute for BackgroundWorker providing the flexibility to call it synchronously or asynchronously, a generically typed Data property, and an OnComplete event. The OnComplete will execute in the thread’s context so synchronization of several threads won’t be a problem. Take a look at it in action:

public class MyThread : ThreadBase<DateTime>
{
    public override void DoWork(object arguement)
    {
        Trace.WriteLine(string.Format("MyThread thread id {0}", 
              System.Threading.Thread.CurrentThread.ManagedThreadId));
        Data = DateTime.Now;
    }
}

What a nicely encapsulated thread! Below we can see how cleanly a MyThread can be used:

MyThread thread = new MyThread();
thread.OnComplete += new ThreadBase<DateTime>.ThreadComplete(thread_OnComplete);
thread.DoWorkAsync();

void thread_OnComplete(ThreadBase<DateTime> thread, DateTime data)
{
    Trace.WriteLine(string.Format("Complete thread id {0}: {1}", 
          Thread.CurrentThread.ManagedThreadId, data));
}

Then I got to thinking what if I wanted the best of both worlds? Thanks to Reflector, I found out how BackgroundWorker’s RunWorkerCompleted event executes in the context of the calling thread. My generic ThreadBaseEx class offers two events: OnCompleteByThreadContext and OnCompleteByCallerContext.

/// <span class="code-SummaryComment"><summary>
</span>/// Abstract base class which performs some work and stores it in a data property
/// <span class="code-SummaryComment"></summary>
</span>/// <span class="code-SummaryComment"><typeparam name="T">The type of data this thread will procure</typeparam>
</span>public abstract class ThreadBaseEx<T>
{
#region Private variables
    private AsyncOperation _asyncOperation;
    private readonly SendOrPostCallback _operationCompleted;
#endregion

#region Ctor
    public ThreadBaseEx()
    {
        _operationCompleted = new SendOrPostCallback(AsyncOperationCompleted);
    }
#endregion

#region Public methods
    /// <span class="code-SummaryComment"><summary>
</span>    /// Does the work asynchronously and fires the OnComplete event
    /// <span class="code-SummaryComment"></summary>
</span>    public void DoWorkAsync()
    {
        DoWorkAsync(null);
    }

    /// <span class="code-SummaryComment"><summary>
</span>    /// Does the work asynchronously and fires the OnComplete event
    /// <span class="code-SummaryComment"></summary>
</span>    /// <span class="code-SummaryComment"><param name="arguement">The arguement object</param>
</span>    public void DoWorkAsync(object arguement)
    {
        _asyncOperation = AsyncOperationManager.CreateOperation(null);
        ThreadPool.QueueUserWorkItem(DoWorkHelper, arguement);
    }

    /// <span class="code-SummaryComment"><summary>
</span>    /// Does the work and populates the Data property
    /// <span class="code-SummaryComment"></summary>
</span>    public void DoWork()
    {
        DoWork(null);
    }

    /// <span class="code-SummaryComment"><summary>
</span>    /// Does the work and populates the Data property
    /// <span class="code-SummaryComment"></summary>
</span>    /// <span class="code-SummaryComment"><param name="arguement">The arguement object</param>
</span>    /// <span class="code-SummaryComment"><remarks>
</span>    /// Can be called to run syncronously, which doesn't fire the OnComplete event
    /// <span class="code-SummaryComment"></remarks>
</span>    public abstract void DoWork(object arguement);
#endregion

#region Private methods
    private void DoWorkHelper(object arguement)
    {
        DoWork(arguement);
        if (OnCompleteByThreadContext != null)
            OnCompleteByThreadContext.Invoke(this, Data);
        _asyncOperation.PostOperationCompleted(this._operationCompleted, arguement);
    }

    private void AsyncOperationCompleted(object arg)
    {
        OnCompleteByCallerContext(this, Data);
    }
#endregion

#region Properties
    public T Data { get; protected set; }
#endregion

#region Events
    /// <span class="code-SummaryComment"><summary>
</span>    /// Delegate which is invoked when the thread has completed
    /// <span class="code-SummaryComment"></summary>
</span>    /// <span class="code-SummaryComment"><param name="thread">The thread.</param>
</span>    /// <span class="code-SummaryComment"><param name="data">The data.</param>
</span>    public delegate void ThreadComplete(ThreadBaseEx<T> thread, T data);

    /// <span class="code-SummaryComment"><summary>
</span>    /// Occurs when the thread completes.
    /// <span class="code-SummaryComment"></summary>
</span>    /// <span class="code-SummaryComment"><remarks>This event operates in the context of the worker thread</remarks>
</span>    public event ThreadComplete OnCompleteByThreadContext;

    /// <span class="code-SummaryComment"><summary>
</span>    /// Occurs when the thread completes.
    /// <span class="code-SummaryComment"></summary>
</span>    /// <span class="code-SummaryComment"><remarks>This event operates in the context of the calling thread</remarks>
</span>    public event ThreadComplete OnCompleteByCallerContext;
#endregion
}

Your encapsulated thread will be the same as above, but now with two events allowing either scenario, depending on what suits.

License

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

About the Author

newkie
Software Developer (Senior) Storm Technology Ltd.
Ireland Ireland
Formerly a C++ client developer, nowadays I’m all about C# and ASP.NET. I work for a consulting firm which specializes in .NET technologies. Over the years I have mastered some and played with many aspects of .NET.
 
Follow my blog as I catalogue the more arcane problems I encounter and their solutions at CodingLifestyle.com

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Mobile
Web01 | 2.8.140721.1 | Last Updated 30 Mar 2011
Article Copyright 2011 by newkie
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid