Tuesday, November 21, 2006

.Net Threads - 3 ways to use them

ThreadStart

In general, one should create a new thread instance for long-running tasks ...

ThreadStart job = new ThreadStart(ThreadJob);
Thread thread = new Thread(job);
thread.Start();

static void ThreadJob()
{
// do something
}

ThreadStart delegate doesn't take any parameters, so the state/information has to be stored somewhere else. WaitCallback can be used to create a new instance of a class and use it to store the state/information. Often the class itself can contain the delegate used for starting the thread.

class WorkerThreadState
{
private WaitCallback _callback;
private object _data;

public WorkerThreadState( WaitCallback callback, object data )
{
  if ( callback == null ) throw new ArgumentNullException( "callback" );
  _callback = callback;
  _data = data;
}

// ThreadStart delegate
  public void Execute()
{
  _callback( _data );
}

// WaitCallback delegate
  static void MyMethod( object state )
{
  Console.WriteLine( "State: " + state );
}

static void Main()
{
  string state = "Hello, background world!";
  WaitCallback wcb = new WaitCallback(MyMethod);
  WorkerThreadState wts = new WorkerThreadState(wcb, state);
  ThreadStart ts = new ThreadStart(wts.Execute);
  Thread t = new Thread(ts);
  t.Start();
}
}

Some points to note here ...

  • MyMethod can't be wrapped with a ThreadStart delegate given different signatures
  • MyMethod is wrapped with a WaitCallback (correct signature)
  • WaitCallback and the state to be supplied to it are wrapped in a new WorkerThreadState instance
  • Since WorkerThreadState is parameterless, Execute method is wrapped with a ThreadStart delegate

In the example above, delegate (.Net 2+) can be used as follows ...

WorkerThreadState wts = new WorkerThreadState(MyMethod, state);
Thread t = new Thread(wts.Execute);
t.Start();

One can also access variables (including local variables and parameters of the "outside" method) within the anonymous method as follows ...

ThreadStart starter = delegate { Fetch (myUrl); };
Thread t = new Thread(starter);
t.Start();

ParameterizedThreadStart [.Net 2.0]

Some points of interest here are ...

  • ParameterizedThreadStart has the same signature as WaitCallback. It can thus be used with methods that accept a single object parameter
  • One can create a thread using an instance of this delegate
  • Thread.Start overload now allows the value to be passed to the new thread
  • This is simple, but only accepts a single parameter and isn't type-safe (just like the options when using thread pool threads)
ParameterizedThreadStart pts = new ParameterizedThreadStart(FetchUrl);
Thread t = new Thread (pts);
t.Start (myUrl);

Or using delegates, this could be rewritten as:

Thread t = new Thread (FetchUrl);
t.Start (myUrl);

ThreadPool

Thread pools should be used only for brief job

Synchronous:

ThreadPool.QueueUserWorkItem

Asynchronous: Allows multiple strongly typed parameters

Stream.BeginRead
delegate.BeginInvoke 

Anonymous methods [.Net 2.0]: One can access variables (including local variables and parameters of the "outside" method) within the anonymous method

WaitCallback callback = delegate (object state) { Fetch (string)state); };
ThreadPool.QueueUserWorkItem (callback, myUrl);

References

http://www.yoda.arachsys.com/csharp/threads/ http://msdn.microsoft.com/msdnmag/issues/06/06/NETMatters/default.aspx