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

Background Thread? Let me count the ways....

By , 14 Aug 2013
Rate this:
Please Sign up or sign in to vote.
Prize winner in Competition "Best overall article of August 2013"
Prize winner in Competition "Best C# article of August 2013"

Introduction

This article (well more of a ditty really) demonstrates ten (previously nine) fabulous ways of doing something on a background thread in C#. It serves little point other than providing something to talk about in the pub when no-one interesting is around.

Background

I was once asked in an interview to describe different ways of doing something on a background thread. They wanted two answers but I gave them three. This was in 2008, and there were probably four ways then – create a new thread and use that, use the ThreadPool, use the Asynchronous Programming Model (APM) via a delegate or use a timer. I suppose the BackgroundWorker was around too, so let's say five then, but no self-respecting person would mention that.

Someone has kindly pointed out that I missed another way which would be to spawn a second process which would have its own threads and do the work there. This doesn't quite fit the criteria of a background thread in my mind, but *is* a way of doing something rather clumsily in the background. So, let's say six!

But time has moved on, and they love re-inventing things so we now have the Task Parallel Library (TPL), Parallel LINQ (PLINQ) and even the Reactive Framework (RX) which can all be coerced into doing a background operation. (If you want to run the RX approach in the example source, you’ll need to get the RX package from NuGet.)

Pretty much every approach ends up assigning the operation to the ThreadPool, they are just different ways of getting it there. Without further ado, here are ten ways of doing something on a background thread. I'm sure there are more, so feel free to advise me of those I've missed and call me an idiot. Also, please note that I've got to write the code and article before my wife gets back from coffee with her friends as we've got to go to Blue Water (a shopping centre in Kent) to buy pants, so won’t have time to check that anything claimed here is correct. Sorry.

The Long Term Commitment

The most obvious way of doing something in the background is to create a thread and do it on that. But, this is discouraged for small operations as it’s an expensive process. Also, a thread typically comes furnished with 1MB stack so creating the things willy-nilly is going to hurt your memory consumption. This approach is best when the background operation is going to be long running:

public void LongTermCommitment()
{
    // create a thread, execute the background method and block until it's done
    Thread thread = new Thread(_ => BackgroundMethod("Long Term Commitment"));
    thread.Start();
    thread.Join();
}

The Gentleman's Approach

Because thread creation is expensive .NET comes with a thread pool which is like a nice home for threads. They are kept locked up and only let out to do something then put back in again, thus avoiding lots of creation and destruction each time.

There’s plenty of information about the ThreadPool all over the internet so let’s not mention it further. I know it, you know it and my dog knows it:

public void TheGentlemansApproach()
{
    // straight onto the threadpool - what could be better?
    ThreadPool.QueueUserWorkItem(_ => BackgroundMethod("The Gentleman's Approach"));
}

The Mentalist

Ah yes, the Asynchronous Programming Model or APM. This has been around since the early days but largely ignored in every bit of code I’ve seen. Is it any wonder? It involves something called an IAsyncResult and that sounds horrible. To do this, simply call BeginInvoke on a delegate and it fires off on the ThreadPool. You then need to call EndInvoke which will block your current thread until the delegate returns. I seem to recall that you must do this or something horrible happens, but can’t remember what:

public void TheMentalist()
{
    // Use the Asyncronous Programming Model (APM) - a bit ugly in my eyes
    BackgroundMethodDelegate x = new BackgroundMethodDelegate(BackgroundMethod);
    IAsyncResult a = x.BeginInvoke("The Mentalist", null, null);
    x.EndInvoke(a);
}

The VB Sissy

Let’s face it; anyone who programs in VB is weak. And although I can’t prove it I suspect when they invented the BackgroundWorker they had the weak in mind. This is for people who need to do something in the background so they don’t lock up their GUI but don’t really get threading, so it has nice methods that post back to the calling thread updates. This is very much of the old fashioned form of using a message loop, so when you call it from a Console App like we are doing here that isn't going to work:

public void TheVbSissy()
{
    // BackgroundWorkers are for wimps. Case closed.
    BackgroundWorker worker = new BackgroundWorker();
    worker.DoWork += delegate { BackgroundMethod("The VB Sissy Approach"); };
    worker.RunWorkerAsync();
}

Someone asked below is there a valid reason not to like the BackgroundWoker other than VB snobbery. This is a fair question and the answer is no, not really. As alluded to above, this is for use when developing a GUI so that any long running or blocking operations don't lock up the user interface. In fact, if you need to do a long-running operation such as loading a large file into memory it is quite a good choice. It will do the work on a background thread and has built in support for reporting back progress and completion.

The golden rule in GUI programming is that only the thread that creates a control can update it, because the message-loop that is used is itself not thread-safe, and the BackgroundWorker takes care of the thread switching for you. I have never used this thing myself, instead favouring creating encapsulated methods on the form which BeginInvoke onto the GUI thread, and thus are thread-safe. Multiple threads can then go bananas as much as they like.

That's how I do it, but each to their own as they say. If you don't want to do that, then go ahead and use the BackgroundWorker. (You sissy)

The Insane Co-Worker

We all have them, and after all why put something directly on the ThreadPool when you can create a timer that fires once, immediately and do the operation in that. Things like this do happen and when I see them usually have to leave the building for a short while to contemplate the failure of mankind whilst choking on a Marlboro Light. Do not do this:

public void TheInsaneCoWorker()
{
    // this requires a certain level of teeth gritting
    Timer timer = new Timer(_ => BackgroundMethod("The Insane Coworker"), null, 0, Timeout.Infinite);
}

For clarity, I am using the System.Threading.Timer here. There is also a System.Timers.Timer which could be used. Both WinForms and WPF also provide different timers, but these post back to the main thread rather than executing on a background thread so cannot be used.

The Task At Hand

Onto TPL, which provides a layer of abstraction over the ThreadPool. This approach embodies the operation in a thing called a Task, which provides richer functionality and control, providing nice features such as cancellation. It’s simple and looks like this:

public void TheTaskAtHand()
{
    // first of many TPL ways
    using (Task task = new Task(() => BackgroundMethod("The Task At Hand")))
    {
        task.Start();
        task.Wait();
    }
}

The Task in The Other Hand

Or, looks like this:

public void TheTaskInOtherHand()
{
    Task.Factory.StartNew(() => BackgroundMethod("The Task In The Other Hand"));
}

The Contemporary Approach

This is quite neat really. We now have a very straight-forward way of invoking something in the background:

public void TheContemporaryApproach()
{
    // pretty neat - one has to admit
    Parallel.Invoke(() => BackgroundMethod("The Contemporary Approach"));
}

The Show Off

I've been fiddling around with RX recently, and it’s essentially LINQ having undergone a sex-change. Things which were pulled are now pushed, and you can set up subscribers which receive the pushed item on a background thread. So here we push the item to a subscriber which handles it, and the thread-switching is all done seamlessly. If you haven’t played around with RX yet, it’s pretty cool once you get your head around the essential idea:

public void TheShowOff()
{
    // RX - push the item into a subscribed method
    Observable.Return("The Show Off", Scheduler.Default).Subscribe(BackgroundMethod);
}

The Second Process

Here's one I didn't think of but has been suggested as a way of doing something in the background, even if it doesn't qualify as a background thread. (By definition, a background thread is a thread in a process which will not prevent the process from terminating while it is still running.)

In this article I have been getting background threads to output a string directly to the console. A second process could do this, but to its console not ours. So, it's a bit feeble but we'll get it to do that and collect the string from the second process standard output stream and display it. Hence it will be output on the main thread but will have been supplied by a second thread in a separate process.

This really is a brute force approach, and one doesn't want to get into the realms of inter-process communication to do something as trivial as this in the background. That said, it's not uncommon to shell out to a separate worker process to get things done but I think defining this as a 'background process' would be better.

To make this work, I have added a switch in our app, so that if a command line argument is supplied it will just output it to the console, otherwise it will do all the different approaches as before. We can then do this:

public void OutOfProcess()
{
    // this is a bit of a kludge as we are writing to the console on the main application thread
    // even so, the operation (writing a string to output) is done in a second process and hence
    // thread, but we have to redirect the output here to see it....
    ProcessStartInfo startInfo = new ProcessStartInfo("StartThreads.exe", "OutOfProcess");
    startInfo.CreateNoWindow = false;
    startInfo.UseShellExecute = false;
    startInfo.RedirectStandardOutput = true;

    Process process = Process.Start(startInfo);
    Console.WriteLine("Approach \"{0}\" sent from second process",
        process.StandardOutput.ReadToEnd());
    process.WaitForExit();
}

Conclusion

You are given a fair amount of choice about how to do things these days and hopefully this demonstrates that point. Personally I think there’s a little too much choice and that leads people to do the same thing in different ways which in turn complicates the development cycle a bit. I suppose we should obey and just use TPL for everything and think of the older ways as obsolete and there for backward compatibility.

Points of Interest

Well, I can’t see any, sadly.

History

  • 8th August 2013: Initial version
  • 13th August 2013: Update to include second process - thanks to thelazydogsback for suggesting this.
  • 14th August 2013: Toned down about BackgroundWorker, sort of - thanks to Mike Cattle for pointing out my code bigotry.

License

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

About the Author

Rob Philpott
Architect
United Kingdom United Kingdom
I am a .NET architect/developer based in London working mostly on financial trading systems. My love of computers started at an early age with BASIC on a 3KB VIC20 and progressed onto a 32KB BBC Micro using BASIC and 6502 assembly language. From there I moved on to the blisteringly fast Acorn Archimedes using BASIC and ARM assembly.
 
I started developing with C++ since 1990, where it was introduced to me in my first year studying for a Computer Science degree at the University of Nottingham. I started professionally with Visual C++ version 1.51 in 1993.
 
I moved over to C# and .NET in early 2004 after a long period of denial that anything could improve upon C++.
 
Recently I did a bit of work in my old language of C++ and I now realise that frankly, it's a total pain in the arse.

Comments and Discussions

 
GeneralMixed feelings PinmemberIvaylo5ev24-Oct-13 21:37 
GeneralMy vote of 5 Pinmembersmatveev4-Oct-13 3:31 
GeneralMy vote of 5 PinmemberTodd Pichler24-Sep-13 2:01 
GeneralMy vote of 1 PinmemberBen Burnett23-Sep-13 18:44 
GeneralRe: My vote of 1 PinprofessionalRob Philpott23-Sep-13 21:53 
Question.Net 4.5 PinmemberMember 1028993623-Sep-13 10:46 
AnswerRe: .Net 4.5 PinprofessionalRob Philpott23-Sep-13 21:56 
GeneralMy vote of 5 PinmemberQwertie23-Sep-13 10:43 
QuestionRunning a console app as a true background process... PinmemberKRucker23-Sep-13 9:51 
AnswerRe: Running a console app as a true background process... PinprofessionalRob Philpott23-Sep-13 10:21 
GeneralMy vote of 5 Pinmembervlad_pol@hotmail.com23-Sep-13 8:25 
GeneralRe: My vote of 5 PinprofessionalRob Philpott23-Sep-13 21:56 
QuestionThreading seems Threatening, seeking advice as a Thread beginner PinmemberBabak Sekandari23-Sep-13 8:23 
AnswerRe: Threading seems Threatening, seeking advice as a Thread beginner PinprofessionalRob Philpott23-Sep-13 10:18 
QuestionTo VB || ! to VB PinmemberMember 904013723-Sep-13 7:13 
GeneralMy vote of 2 PinmemberMahBulgaria23-Sep-13 2:44 
GeneralMy vote of 5 PinmemberSingyuen Yip22-Sep-13 21:08 
GeneralMy vote of 3 PinprofessionalPaulo Zemek17-Sep-13 16:39 
GeneralRe: My vote of 3 PinprofessionalRob Philpott21-Sep-13 22:20 
GeneralRe: My vote of 3 [modified] PinprofessionalPaulo Zemek22-Sep-13 4:06 
GeneralMy vote of 5 PinprofessionalRenju Vinod16-Sep-13 18:57 
GeneralMy vote of 5 Pinprofessionalkounadg11-Sep-13 20:37 
GeneralMy vote of 2 PinprofessionalAthari10-Sep-13 21:38 
GeneralMy vote of 5 PinmemberBillWoodruff8-Sep-13 2:30 
QuestionGreat article PinmemberMilan Stanacev6-Sep-13 22:30 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140415.2 | Last Updated 14 Aug 2013
Article Copyright 2013 by Rob Philpott
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid