Click here to Skip to main content
15,867,308 members
Articles / Programming Languages / C#
Article

Multithreading in .NET

Rate me:
Please Sign up or sign in to vote.
4.48/5 (23 votes)
20 May 20016 min read 459.4K   4.5K   160   29
An article on multithreading in .NET. Three different ways of creating threads in .NET are discussed: simple threads, timers and thread pool.

Introduction

Asynchronous processing and background processing was always a must for serious programs serving complex user needs. The Windows NT platform offers a great way to accomplish this, but the implementation was sometimes tedious and always labor intensive. It is the reason why I first studied multithreading options offered by the .NET framework.

This article shows three different ways of creating (and using) threads, without communication and synchronization between them.

Before you start writing multithreaded programs, bear in mind some guidelines (from MSDN – Threading design guidelines):

  • Avoid providing static methods that mutate static state
  • Design for server environment
  • Instances do not need to be thread safe
  • Static states must be thread safe

Simple threading

Let’s first try the simplest way to create a new thread. The starting point for such a thread is void method with no parameters. Thread creation is done in two steps:

  1. Create a delegate object, initialized with our method
  2. Use this object as an initialization parameter when creating a new Thread object.

When our Thread object is created, call the Start method and background processing will commence.

public class MainClass{
	public void threadMethod(){
		...
	}
	public static void Main(){
		...
		ThreadStart entry = new ThreadStart(threadMethod ) ;
		Thread thread1 = new Thread( entry ) ;
		thread1.Start() ;
		...
	}
}

This sample hides the fact that using C# we do not have a global function, and usually you will start the thread on static class method.

public class Tester{
	public static void Test(){
		...
	}
}
public class MainClass{
	public static void Main()
		...
		ThreadStart entry = new ThreadStart( Tester.Test ) ;
		Thread thread1 = new Thread( entry ) ;
		thread1.Start() ;
		...
	}
}

Now we came to the first beautiful part of the .NET framework. We can start threads on class instances and create real living objects.

public class Tester{
	public void Test(){
		...
	}
}
public class MainClass{
	public static void Main()
		...
		Tester testObject = new Tester() ;
		ThreadStart entry = new ThreadStart( testObject.Test ) ;
		Thread thread1 = new Thread( entry ) ;
		thread1.Start() ;
		...
	}
}

Timer threads

A common use of threads is for all kinds of periodical updates. Under Win32 we have two different ways: window timers and time limited waiting for events. .NET offers three different ways:

  • Windows timers with the System.WinForms.Timer class
  • Periodical delegate calling with System.Threading.Timer class (works on W2K only)
  • Exact timing with the System.Timers.Timer class

For inexact timing we use the window timer. Events raised from the window timer go through the message pump (together with all mouse events and UI update messages) so they are never exact. The simplest way for creating a WinForms.Timer is by adding a Timer control onto a form and creating an event handler using the control's properties. We use the Interval property for setting the number of milliseconds between timer ticks and the Start method to start ticking and Stop to stop ticking. Be careful with stopping, because stopped timers are disabled and are subject to garbage collection. That means that stopped timers can not be started again.

The System.Threading.Timer class is a new waiting thread in the thread pool that periodically calls supplied delegates. Currently it works on Windows 2000 only. Documentation for this class is not finished yet (beta 1). To use it you must perform several steps:

  1. Create state object which will carry information to the delegate
  2. Create TimerCallback delegate with a method to be called. You can use static or instance methods.
  3. Create a Timer object with time to wait before first call and periods between successive calls (as names of this two parameters suggests, first parameter should be lifetime of this timer object, but it just don’t work that way)
  4. Change the Timer object settings with the Change method (same remark on parameters apply)
  5. Kill the Timer object with the Dispose method

If we put this in code we get:

using System;
using System.Threading;

// class for storing current state
public class StateObj{
	...
}

// class that will work on timer request
public class TimerClass{
	public void TimerKick( object state ){
		StateObj param = (StateObj)state ;
		// do some work with param
	}
}

// usage block
{
	...
	// prepare state object
	StateObj state = new StateObj() ;
	// prepare testing object – not necessary when cb is static
	TimerClass testObj = new TimerClass() ;
	// prepare callback delegate – careful on static methods
	TimerCallback tcb = new TimerCallback( obj.TimerKick ) ;
	// make timer
	long waitTime = 2000 ;	// wait before first tick in ms
	long periodTime = 500 ;	// timer period
	Timer kicker = new Timer( tcb, state, waitTime, periodTime ) ;
	// do some work
	...
	kicker.Change( waitTime, periodTime ) ;
	// do some more work
	...
	kicker.Dispose() ;
	...
}

For waitTime and periodTime you can use TimeSpan types if it makes more sense. In the supplied sample you can see that the same state object is used on both timers. When you run the sample the state values printed are not consecutive. I left it like this to show how important synchronization is in a multithread environment.

The final timing options come from the System.Timers.Timer class. It represents server-based timer ticks for maximum accuracy. Ticks are generated outside of our process and can be used for watch-dog control. In the sameSystem.Timers namespace you can find the Schedule class which gives you the ability to schedule timer events fired at longer time intervals.

System.Timers.Timer class is the most complete solution for all time fired events. It gives you the most precise control and timing and is surprisingly simple to use.

  1. Create the Timer object. You can a use constructor with interval setting.
  2. Add your event handler (delegate) to the Tick event
  3. Set the Interval property to the desired number of milliseconds (default value is 100 ms)
  4. Set the AutoReset property to false if you want the event to be raised only once (default is true – repetitive raising)
  5. Start the ticking with a call to the Start() method, or by setting Enabled property to true.
  6. Stop the ticking with call to the Stop() method or by setting the Enabled property to false.
using System.Timers ;

void TickHandler( object sender, EventArgs e ){
	// do some work
}

// usage block
{
	...
	// create timer
	Timer kicker = new Timer() ;
	kicker.Interval = 1000 ;
	kicker.AutoReset = false ;

	// add handler
	kicker.Tick += new EventHandler( TickHandler ) ;

	// start timer
	kicker.Start() ;

	// change interval
	kicker.Interval = 2000 ;

	// stop timer
	kicker.Stop() ;

	// you can start and stop timer againg
	kicker.Start() ;
	kicker.Stop() ;
	...
}

I should mention a few things about using the Timers.Timer class:

  • In VS Beta 1 you must add a reference to System.Timers namespace by hand.
  • Whenever you use the Timers namespace together with System.WinForms or System.Threading, you should reference the Timer classes with the full name to avoid ambiguity.
  • Be careful when using Timers.Timer objects. You may find them a lot faster than the old windows timers approach (think why). Do not forget synchronize data access.

Thread pooling

The idea for making a pool of threads on the .NET framework level comes from the fact that most threads in multithreaded programs spend most of the time waiting for something to happen. It means that thread entry functions contain endless loops which calls real working functions. By using the ThreadPool type object preparing working functions is simpler and for bonus we get better resource usage.

There are two important facts relating to ThreadPool object.

  • There is only one ThreadPool type object per process
  • There is only one working thread per thread pool object

The most useful use of a ThreadPool object is to add a new thread with a triggering event to the thread pool. i.e.. "when this event happens do this". For using ThreadPool this way you must perform following steps:

  1. Create event
  2. Create a delegate of type WaitOrTimerCallback
  3. Create an object which will carry status information to the delegate.
  4. Add all to thread pool
  5. Set event

In C#:

// status information object
public class StatusObject{
	// some information
}

// thread entry function
public void someFunc( object obj, bool signaled ){
	// do some clever work
}

// usage block
{
	...
	// create needed objects
	AutoResetEvent myEvent = new AutoResetEvent( false ) ;
	WaitOrTimerCallback myThreadMethod = new WairOrTimerCallback( someFunc ) ;
	StatusObject statusObject = new StatusObject() ;

	// decide how thread will perform
	int timeout = 10000 ;   // timeout in ms
	bool repetable = true ; // timer will be reset after event fired or timeout

	// add to thread pool
	ThreadPool.RegisterWaitForSingleObject( myEvent, myThreadMethod,
						statusObject, timeout, repetable ) ;
	...
	// raise event and start thread
	myEvent.Set() ;
	...
}

A less common use of a thread pool will be (or at least should be, be aware of misuse) adding threads to be executed when the processor is free. You could think of this kind of usage as "OK, I have this to do, so do it whenever you have time". Very democratic way of handling background processing which can stop your program quickly. Remember that inside the thread pool you have only one thread working (per processor).

Using thread pool this way is even simpler:

  1. Create a delegate of type WaitCallback
  2. Create an object for status information, if you need it
  3. Add to thread pool
// status information object
public class StatusObject{
	// some information
}

// thread entry function
public void someFunc( object obj, bool signaled ){
	// do some clever work
}

// usage block
{
	...
	// create needed objects
	WaitCallback myThreadMethod = new WairOrTimerCallback( someFunc ) ;
	StatusObject statusObject = new StatusObject() ;

	// add to thread pool
	ThreadPool.QueueUserWorkItem( myThreadMethod, statusObject ) ;
	...
}

Some notes on thread pool:

  • Don’t use a thread pool for threads that perform long calculations. You only have one thread per processor actually working.
  • System.Thread.Timer type objects are one thread inside thread pool
  • You have only one thread pool per process

Conclusion

This article shows the way I used to find out secrets of .NET multithreading. When you try using this, be careful on thread synchronization. This is also subject of my next article, where I will show all different ways of synchronization that .NET offers.

For more information read articles in MSDN library:

  • .NET framework design guidelines
  • Threading design guidelines
  • Asynchronous execution

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Software Developer (Senior)
Slovenia Slovenia
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionVideo Tutorial Pin
Irek Janek4-Dec-14 6:42
Irek Janek4-Dec-14 6:42 
GeneralMy vote 5 Pin
Aarti Meswania25-Mar-14 3:20
Aarti Meswania25-Mar-14 3:20 
GeneralMy vote of 5 Pin
Espiritu28-Oct-10 4:27
Espiritu28-Oct-10 4:27 
GeneralMy vote of 5 Pin
Subhasis Mittra21-Sep-10 21:02
professionalSubhasis Mittra21-Sep-10 21:02 
GeneralInteresting subject but Pin
Gebbetje6-Jan-10 0:40
Gebbetje6-Jan-10 0:40 
Questionwhy can't i use ThreadPool for long calculations? Pin
DreamSurfer1-Jul-09 21:43
DreamSurfer1-Jul-09 21:43 
Generalupdate form [modified] Pin
kalyan21-May-08 7:39
kalyan21-May-08 7:39 
GeneralMulti- Core Processor and Multiple Processor Servers Pin
ivix4u13-Mar-08 23:14
ivix4u13-Mar-08 23:14 
GeneralTypo mistakes Pin
Shivan Raptor14-Aug-06 16:17
Shivan Raptor14-Aug-06 16:17 
GeneralThread got killed/recycled in a WebService app Pin
comet_little2-Aug-06 5:32
comet_little2-Aug-06 5:32 
GeneralRe: Thread got killed/recycled in a WebService app Pin
Nirosh10-Sep-06 21:38
professionalNirosh10-Sep-06 21:38 
GeneralRe: Thread got killed/recycled in a WebService app Pin
CoolVini7-Aug-08 10:19
CoolVini7-Aug-08 10:19 
Generalmultithreading Pin
kavithanallamothu16-Mar-05 1:15
kavithanallamothu16-Mar-05 1:15 
GeneralQuestion please... Pin
profoundwhispers30-Sep-03 0:48
profoundwhispers30-Sep-03 0:48 
QuestionSchedule class ? Pin
belzu18-Aug-03 1:17
belzu18-Aug-03 1:17 
AnswerRe: Schedule class ? Pin
Dream8Lee9-Mar-13 2:11
Dream8Lee9-Mar-13 2:11 
GeneralArticle Feedback Pin
Peter Kiss14-Nov-02 23:00
Peter Kiss14-Nov-02 23:00 
GeneralRe: Article Feedback Pin
Anonymous29-Mar-03 7:54
Anonymous29-Mar-03 7:54 
GeneralRe: Article Feedback Pin
Mike5087410-Sep-07 6:21
Mike5087410-Sep-07 6:21 
GeneralRe: Article Feedback Pin
DreamSurfer1-Jul-09 21:29
DreamSurfer1-Jul-09 21:29 
GeneralRe: Article Feedback Pin
Dream8Lee9-Mar-13 2:07
Dream8Lee9-Mar-13 2:07 
Generalcrear un grupo de hilos Pin
2-Nov-01 5:01
suss2-Nov-01 5:01 
GeneralRe: crear un grupo de hilos Pin
5-Nov-01 23:14
suss5-Nov-01 23:14 
sorry I don't understand
GeneralRe: crear un grupo de hilos Pin
Jesus Oliva21-Dec-02 14:47
Jesus Oliva21-Dec-02 14:47 
GeneralLepe pozdrave iz QuickTime d.o.o. Pin
Rok24-May-01 21:05
Rok24-May-01 21:05 

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

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