The Thread Pool and Asynchronous Methods

The point of the thread pool is to avoid creating lots of threads for short tasks. Thread creation isn't particularly cheap, and if you start lots of threads, each doing only just enough work to warrant being run on a different thread in the first place, the cost of creation could significantly hamper performance. The thread pool solves that by having a "pool" of threads which have already been created and are just waiting for work items. When they've finished executing a work item, they then wait for the next one, etc. By default, the thread pool has 25 threads per processor. Note that the thread pool isn't just used for whatever asynchronous calls you make - the .NET framework libraries use it as well, and things can go badly wrong (usually resulting in a deadlock) if all the threads are used and some of them depend on other tasks which are scheduled. (For instance, if one thread is waiting for the results of another work item, but that work item is never run because there are no free threads.) This is a good reason to avoid using the thread pool for particularly long-running tasks. Personally I usually stick to creating a new thread for anything but pretty trivial tasks. If the thread's going to be running for more than a few seconds, the cost of creating the thread is going to be relatively insignificant.

Note that none of the samples below have any locking or volatility in to ensure that "fresh" values are seen. I haven't seen any code in any other samples, either. It's not a problem so long as there's a memory barrier in both the calling thread and the thread pool thread, but I haven't seen any guarantees of that. I would expect that there would be a memory barrier involved in each thread just to get the whole thing up and running in the first place, but as I say I haven't seen it guaranteed anywhere.

You can tell whether or not a thread is from the thread pool or not using Thread.IsThreadPoolThread. Thread pool threads are background threads - they don't prevent the runtime from exiting when all non-background threads have completed. There are various different ways of using the thread pool. Here's a brief description of each of them (except timers which have their own section, following this one):

ThreadPool.QueueUserWorkItem()

This is probably the simplest way of executing code in a thread pool thread. You simply provide a WaitCallback delegate (it doesn't return anything, and takes a single parameter of type object), and optionally the object to pass as the parameter to the callback when it is executed. If you don't specify a parameter, the callback will be passed null instead. Here's a sample program to demonstrate it:

using System;
using System.Threading;

public class Test
{
                  static void Main()
    {
        ThreadPool.QueueUserWorkItem(new WaitCallback(PrintOut), "Hello");
        
                  // Give the callback time to execute - otherwise the app
                  // may terminate before it is called
        Thread.Sleep(1000);
    }
    
                  static void PrintOut (object parameter)
    {
        Console.WriteLine(parameter);
    }
}

There is no built-in way of waiting for your callback to be executed, although you can of course signal the end of the callback using "normal" threading mechanisms (Monitor.Wait/Pulse etc).

Calling BeginInvoke on a delegate

It's relatively hard to find documentation on this topic, but when you create a delegate, the compiler generates three methods for you: Invoke, BeginInvoke and EndInvoke. Invoke is used to execute the delegate synchronously (i.e. a line of code such as myDelegate(); is actually compiled as myDelegate.Invoke();). The other two methods are for asynchronous execution, and must always be called as a pair - every BeginInvoke must be matched by a call to EndInvoke somewhere to guarantee that you don't leak resources. BeginInvoke takes the same parameters as the delegate itself does, plus another two parameters - an AsyncCallback which is called after the delegate has executed, and an object parameter which is made available through the AsyncState property of the IAsyncResult parameter which is passed to the AsyncCallback. (This is typically used to pass the delegate which is being invoked, to make it easy to call EndInvoke.) The call to EndInvoke can be made to find the return value of the executed delegate. Don't worry if it sounds confusing - hopefully this example will make it somewhat simpler:

using System;
using System.Threading;

public class Test
{
                  delegate int TestDelegate(string parameter);
    
                  static void Main()
    {
        TestDelegate d = new TestDelegate(PrintOut);

        d.BeginInvoke("Hello", new AsyncCallback(Callback), d);
        
                  // Give the callback time to execute - otherwise the app
                  // may terminate before it is called
        Thread.Sleep(1000);
    }
    
                  static int PrintOut (string parameter)
    {
        Console.WriteLine(parameter);
                  return 5;
    }
    
                  static void Callback (IAsyncResult ar)
    {
        TestDelegate d = (TestDelegate)ar.AsyncState;
        Console.WriteLine ("Delegate returned {0}", d.EndInvoke(ar));
    }
}

The call to BeginInvoke returns an IAsyncResult which can be used to call EndInvoke, and you don't have to pass a callback delegate to be executed if you don't want to - just pass null as the last but one parameter to BeginInvoke. (You may still wish to pass in meaningful last parameter, as that will be available in the returned IAsyncResult's AsyncState property, just as it would be in the callback case.)

The call to EndInvoke blocks until the delegate has finished executing - it's sort of like Thread.Join, but for a specific asynchronous delegate execution rather than a specific thread. Of course, when you call it from a callback delegate, it won't need to block as the callback will only execute after the delegate has finished anyway. Here's an example using EndInvoke from the original thread instead of using a callback:

                  using System;
using System.Threading;

public class Test
{
                  delegate int TestDelegate(string parameter);
    
                  static void Main()
    {
        TestDelegate d = new TestDelegate(PrintOut);

        IAsyncResult ar = d.BeginInvoke("Hello", null, null);

        Console.WriteLine ("Main thread continuing to execute...");
        
                  int result = d.EndInvoke(ar);
        
        Console.WriteLine ("Delegate returned {0}", result);
    }
    
                  static int PrintOut (string parameter)
    {
        Console.WriteLine(parameter);
                  return 5;
    }
}

Sometimes having to call EndInvoke is inconvenient - you often want "fire and forget" semantics where you don't care about the result or indeed when exactly the delegate has finished executing. Many articles suggest just calling BeginInvoke and not bothering with EndInvoke. This may work without leaking resources - but it's not guaranteed to, and even if it does now, it may not in the future. Here is a utility class (adapter from a mailing list post which allows you to call FireAndForget to execute a delegate asynchronously without worrying about EndInvoke:

using System;
using System.Threading;

public class ThreadUtil
{
    
                  /// <summary>
                  /// Delegate to wrap another delegate and its arguments
                  /// </summary>
                  delegate void DelegateWrapper (Delegate d, object[] args);

                  /// <summary>
                  /// An instance of DelegateWrapper which calls InvokeWrappedDelegate,
                  /// which in turn calls the DynamicInvoke method of the wrapped
                  /// delegate.
                  /// </summary>
                  static DelegateWrapper wrapperInstance = new DelegateWrapper (InvokeWrappedDelegate);
    
                  /// <summary>
                  /// Callback used to call <code>EndInvoke</code> on the asynchronously
                  /// invoked DelegateWrapper.
                  /// </summary>
                  static AsyncCallback callback = new AsyncCallback(EndWrapperInvoke);

                  /// <summary>
                  /// Executes the specified delegate with the specified arguments
                  /// asynchronously on a thread pool thread.
                  /// </summary>
                  public static void FireAndForget (Delegate d, params object[] args)
    {
                  // Invoke the wrapper asynchronously, which will then
                  // execute the wrapped delegate synchronously (in the
                  // thread pool thread)
        wrapperInstance.BeginInvoke(d, args, callback, null);
    }

                  /// <summary>
                  /// Invokes the wrapped delegate synchronously
                  /// </summary>
                  static void InvokeWrappedDelegate (Delegate d, object[] args)
    {
        d.DynamicInvoke(args);
    }

                  /// <summary>
                  /// Calls EndInvoke on the wrapper and Close on the resulting WaitHandle
                  /// to prevent resource leaks.
                  /// </summary>
                  static void EndWrapperInvoke (IAsyncResult ar)
    {
        wrapperInstance.EndInvoke(ar);
        ar.AsyncWaitHandle.Close();
    }
}

When you provide FireAndForget with a delegate to execute, it actually invokes an internal delegate asynchronously, and that delegate executes the one you provided synchronously. This gives the effective result of the delegate you provided begin executed asynchronously, but allows the helper class to call EndInvoke on the delegate that it knows about - the Delegate class itself doesn't provide BeginInvoke or EndInvoke methods, otherwise this extra step would be unnecessary. Note the call to ar.AsyncWaitHandle.Close(). This prevents the WaitHandle leaking until garbage collection. The leak wouldn't cause any problems in most cases (unlike, for instance, file handles leaking), but in situations where FireAndForget would be called many, many times in quick succession, you could end up with a vast number of handles until the garbage collector started finalizing them. (This is also a bad thing in terms of performance - you shouldn't leave things to be finalised when it can be avoided.) The earlier sample code omitted this step for simplicity, but it's worth being aware of.

BeginRead (etc)

There are various methods in the standard library which come in BeginXXX, EndXXX pairs, such as Stream.BeginRead and Stream.EndRead. These almost all follow the same format, which is quite like calling BeginInvoke and EndInvoke on a delegate: you call BeginXXX with some "normal" parameters, an AsyncCallback parameter and a "state" parameter. The callback is executed asynchronously when the operation (such as reading from a stream) has completed. The callback can then use EndXXX to get the results of the operation. There has been some discussion on the newsgroups as to whether calls such as Stream.BeginRead use I/O completion ports, which are a very efficient way of performing I/O asynchronously without using one thread per operation. It seems likely that they do, so even after the first callback has started executing on a thread pool thread, if you need to do more of the same kind of operation (as you frequently will with something like a network stream, where you probably haven't read the whole thing in one go) it's a good idea to keep using asynchronous calls rather than the synchronous forms. Here's an example which downloads this page asynchronously:

using System;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;

public class Test
{
                  static readonly object finishedLock = new object();
                  const string PageUrl = @"https://jonskeet.uk/csharp/threads/threadpool.html";
    
                  static void Main()
    {

        WebRequest request = WebRequest.Create(PageUrl);
        RequestResponseState state = new RequestResponseState();
        state.request = request;
        
                  // Lock the object we'll use for waiting now, to make
                  // sure we don't (by some fluke) do everything in the other threads
                  // before getting to Monitor.Wait in this one. If we did, the pulse
                  // would effectively get lost!
                  lock (finishedLock)
        {
            request.BeginGetResponse(new AsyncCallback(GetResponseCallback), state);
        
            Console.WriteLine ("Waiting for response...");
            
                  // Wait until everything's finished. Normally you'd want to
                  // carry on doing stuff here, of course.
            Monitor.Wait(finishedLock);
        }
    }
    
                  static void GetResponseCallback (IAsyncResult ar)
    {
                  // Fetch our state information
        RequestResponseState state = (RequestResponseState) ar.AsyncState;

                  // Fetch the response which has been generated
        state.response = state.request.EndGetResponse(ar);

                  // Store the response stream in the state
        state.stream = state.response.GetResponseStream();
        
                  // Stash an Encoding for the text. I happen to know that
                  // my web server returns text in ISO-8859-1 - which is
                  // handy, as we don't need to worry about getting half
                  // a character in one read and the other half in another.
                  // (Use a Decoder if you want to cope with that.)
        state.encoding = Encoding.GetEncoding(28591);
        
                  // Now start reading from it asynchronously
        state.stream.BeginRead(state.buffer, 0, state.buffer.Length,
                  new AsyncCallback(ReadCallback), state);
    }
    
                  static void ReadCallback (IAsyncResult ar)
    {
                  // Fetch our state information
        RequestResponseState state = (RequestResponseState) ar.AsyncState;
        
                  // Find out how much we've read
                  int len = state.stream.EndRead(ar);
        
                  // Have we finished now?
                  if (len==0)
        {
                  // Dispose of things we can get rid of
            ((IDisposable)state.response).Dispose();
            ((IDisposable)state.stream).Dispose();
            ReportFinished (state.text.ToString());
                  return;
        }
        
                  // Nope - so decode the text and then call BeginRead again
        state.text.Append(state.encoding.GetString(state.buffer, 0, len));
        
        state.stream.BeginRead(state.buffer, 0, state.buffer.Length,
                  new AsyncCallback(ReadCallback), state);        
    }
    
                  static void ReportFinished (string page)
    {
        Console.WriteLine ("Read text of page. Length={0} characters.", page.Length);
                  // Assume for convenience that the page length is over 50 characters!
        Console.WriteLine ("First 50 characters:");
        Console.WriteLine (page.Substring(0, 50));
        Console.WriteLine ("Last 50 characters:");
        Console.WriteLine (page.Substring(page.Length-50));
        
                  // Tell the main thread we've finished.
                  lock (finishedLock)
        {
            Monitor.Pulse(finishedLock);
        }
    }
    
                  class RequestResponseState
    {
                  // In production code, you may well want to make these properties,
                  // particularly if it's not a private class as it is in this case.
                  internal WebRequest request;
                  internal WebResponse response;
                  internal Stream stream;
                  internal byte[] buffer = new byte[16384];
                  internal Encoding encoding;
                  internal StringBuilder text = new StringBuilder();
    }
}

Note that there is no exception handling code. This is purely to keep the code simple here - it doesn't mean you don't need exception handling in your real code. Unfortunately it's harder to make sure that you don't fail to dispose of unmanaged resources in error situations when using asynchronous methods: the using statement is no use as it only works within one thread - if you did put using statements around the BeginRead (etc) method calls, you'd end up disposing of the stream before it had time to finish reading, which would be disastrous.

Of course, you could avoid asynchronous delegates entirely, not specifying any, and just call EndRead (etc) immediately after BeginRead in the main thread, just as with BeginInvoke and EndInvoke on delegates. Just like with delegates, you must call the matching EndXXX method to avoid potential resource leaks. It's less likely that you'll fail to do so in this case, of course, as the methods tend to return useful information. You should consider exceptions when devising your strategy for making these calls, however.


Next page: Timers
Previous page: Threading In Windows Forms

Back to the main C# page.