Please note: this page is now obsolete. It is now part of my larger article about threading.
In the C# newsgroup, quite a few people have asked how parameters should be passed to
new threads. In other words, if a new thread needs specific information in order to
run (as it often does), how do you get that information to the thread?
The ThreadStart
delegate doesn't take any parameters, so the information
has to be stored somewhere else. Typically, this means creating a new instance of
a class, and using that instance to store the information. For example, we might have
a program which needs to fetch the contents of various URLs, and wants to do it in
the background. You could write code like this:
public class UrlFetcher { private string url public UrlFetcher (string url) { this.url = url; } public void Fetch() { // use url here } } [... in a different class ...] UrlFetcher fetcher = new UrlFetcher (myUrl); new Thread (new ThreadStart (fetcher.Fetch)).Start(); |
In some cases, you actually just wish to call a method in some class (possibly the currently executing class) with a specific parameter. In that case, you may wish to use a nested class whose purpose is just to make this call - the state is stored in the class, and the delegate used to start the thread just calls the "real" method with the appropriate parameter. (Note that the object on which to call the method in the first place will also be required as state unless the method is static.)
ThreadPool
instead
One choice you should always be aware of when starting a new thread is whether
it would actually make more sense to use ThreadPool
, which will
recycle threads for you, and queue items if the pool is full. ThreadPool
is a good choice for jobs which are fairly short and frequent, as it avoids wasting
time creating and destroying threads.
To add a job to the pool, you merely need to call
ThreadPool.QueueUserWorkItem
with a WaitCallback
delegate. One incidental advantage of using a WaitCallback
is
that you can also specify the "state" to use when you queue the work item,
and this is presented to the delegate as a method parameter. It's not
strongly typed however (the passed type is just object
) and I
would suggest having a more strongly typed method which is called by the
WaitCallback
delegate and which should be used in preference
elsewhere in code. For instance, you might define two methods to fetch
URLs as:
// You'd use this method elsewhere in code, if you needed it public void Fetch (string url) { this.url = url; } // You'd use this as the WaitCallback delegate to queue in the thread pool private void Fetch(object url) { Fetch ((string) url); } // And you'd call it like this: ThreadPool.QueueUserWorkItem (new WaitCallback (Fetch), myUrl); |
Another alternative (which also uses the thread pool internally)
is to call a delegate asynchronously. This has the advantage of allowing you to
specify more than one parameter, and have those parameters checked in terms of type
at compile-time. This relies on the C# compiler adding a couple of methods to the
declared delegate type: BeginInvoke
and EndInvoke
.
The BeginInvoke
method always takes whatever parameters the delegate
itself does, along with an AsyncCallback
parameter and a plain
object
parameter as state. It returns an IAsyncResult
reference. When BeginInvoke
is called, a work item is placed on
the thread pool work queue, and when it executes the delegate is called with the
specified parameters. When the delegate has completed, the AsyncCallback
parameter delegate is called with the resulting IAsyncResult
reference
which can be used to find out the original state parameter, the delegate itself,
the return value of the operation (via the delegate), and more.
Note that to find out the original delegate you need to cast the
IAsyncResult
reference to AsyncResult
. I'm not aware of
why the framework is designed in this way, but if you don't like to make that cast
you can always provide the delegate itself as the state parameter.
The EndInvoke
method only takes an IAsyncResult, and has the same return
type as the delegate itself, returning the result of the delegate's execution.
Here's a complete example:
using System; using System.Threading; using System.Runtime.Remoting.Messaging; class AsyncTest { delegate string UrlFetcher(string url); public static void Main() { UrlFetcher u = new UrlFetcher (Fetch); u.BeginInvoke ("some url", new AsyncCallback (AfterFetch), "this is state"); // Just to demonstrate stuff going on while // the fetch happens... for (int i=0; i < 10; i++) { Console.WriteLine ("Foreground thread counter: {0}", i); Thread.Sleep(1000); } } static string Fetch (string url) { // Just to simulate it taking a while to fetch the // contents Thread.Sleep (5000); return "Contents of the url"; } static void AfterFetch (IAsyncResult result) { Console.WriteLine ("Delegate completed."); Console.WriteLine (" State: {0}", result.AsyncState); AsyncResult async = (AsyncResult) result; UrlFetcher fetcher = (UrlFetcher) async.AsyncDelegate; Console.WriteLine (" Contents: {0}", fetcher.EndInvoke(result)); } } |
The output of the above is (in one test run - it might vary very slightly because
the counter may get a bit further before the delegate's Sleep
call
returns):
Foreground thread counter: 0 Foreground thread counter: 1 Foreground thread counter: 2 Foreground thread counter: 3 Foreground thread counter: 4 Delegate completed. State: this is state Contents: Contents of the url Foreground thread counter: 5 Foreground thread counter: 6 Foreground thread counter: 7 Foreground thread counter: 8 Foreground thread counter: 9 |
See the MSDN documentation for the BeginRead
and EndRead
operations in the System.IO.Stream
class for more examples of how this
kind of asynchronous operation works.
Important note: You should always call EndInvoke
on
delegates which are executed asynchronously, even if you don't actually care about the
result of the operation - otherwise you may provoke a memory leak. Fortunately there
are ways to make this easier,
as
shown in the Advanced-DotNet mailing list archives.
One of the future enhancements to C# is almost certain to be anonymous methods.
These allow you to specify blocks of code as methods within other methods, and use those methods
as delegates. You can access variables (including local variables and parameters of the "outside" method)
within the anonymous method. For example, using an anonymous method to fetch a URL using a normal
ThreadStart
delegate:
ThreadStart starter = new ThreadStart() { Fetch (myUrl); }; new Thread(starter).Start(); |
(This could have all been expressed within a single step, creating both the thread
and the delegate in the same line of code, but I believe the above is more readable.)
Here's similar code to use a WaitCallback
and queue the job in
ThreadPool
:
WaitCallback callback = new WaitCallback (state) { Fetch ((string)state); }; ThreadPool.QueueUserWorkItem (callback, myUrl); |
Note the way that the state is declared. This is, of course, subject to change. For more details about anonymous methods and other potential enhancements to C#, see the Microsoft C# Language Future Features page.
Back to the main page.