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()
{
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()
{
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()
{
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()
{
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()
{
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()
{
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()
{
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()
{
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()
{
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.
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.