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

Using events for thread synchronization

Rate me:
Please Sign up or sign in to vote.
4.74/5 (35 votes)
27 Mar 2002CPOL5 min read 377K   2K   67   74
An introduction to using signaled events for thread synchronization in .NET

Introduction

Whenever you have multiple threads in your program, which is just about always, and whenever those multiple threads might have to access the same resource, which is also a very probable contingency, you will need to incorporate some kind of thread synchronization technique. There are threads that will only access a resource in a read-only manner. Let's call them ReadThreads and then there are threads that will write to a resource. We call them WriteThreads or at least let's call them that for now. If a thread reads and writes a shared resource it will still be a WriteThread. The sample application is a windows forms application created using C# and which has three buttons, one for each case. You need to try out each button to see what happens in each of the cases we discuss below.

Case 1 - No synchronization

Alright let's imagine a situation where we have two ReadThreads that run in parallel and also access a shared object. In addition there is also a WriteThread that starts before the ReadThreads and which sets a valid value for the shared object. I have used Thread.Sleep to simulate processing time in the sample code snippets below.

C#
Thread t0 = new Thread(new ThreadStart(WriteThread));
Thread t1 = new Thread(new ThreadStart(ReadThread10));
Thread t2 = new Thread(new ThreadStart(ReadThread20));
t0.IsBackground=true;
t1.IsBackground=true;
t2.IsBackground=true;
t0.Start();
t1.Start();
t2.Start();

As you can see, we have started the WriteThread and then immediately started our two ReadThreads. I'll show the code snippets for these functions below:-

C#
public void WriteThread()
{
	Thread.Sleep(1000);
	m_x=3;
}	
C#
public void ReadThread10()
{
	int a = 10;
	for(int y=0;y<5;y++)
	{
		string s = "ReadThread10";
		s = s + " # multiplier= ";
		s = s + Convert.ToString(a) + " # ";
		s = s + a * m_x;
		listBox1.Items.Add(s);
		Thread.Sleep(1000);
	}
}
C#
public void ReadThread20()
{
	int a = 20;
	for(int y=0;y<5;y++)
	{
		string s = "ReadThread20";
		s = s + " # multiplier= ";
		s = s + Convert.ToString(a) + " # ";
		s = s + a * m_x;
		listBox1.Items.Add(s);
		Thread.Sleep(1000);
	}
}

When we run the program, we get the output shown below :-

Aha! So we have got the first two values wrong, one from each thread. What happened was that the ReadThreads started executing before the WriteThread had finished it's job. This is a totally unwanted situation and we should surely try and do something to avoid this.

Case 2 - Synchronization [One WriteThread - Many ReadThreads]

Now we are going to solve the problem we faced in Case 1. We'll use the ManualResetEvent thread synchronization class. As before we start the WriteThread and the two ReadThreads. The only difference is that we use safe versions of these threads.

C#
Thread t0 = new Thread(new ThreadStart(SafeWriteThread));
Thread t1 = new Thread(new ThreadStart(SafeReadThread10));
Thread t2 = new Thread(new ThreadStart(SafeReadThread20));
t0.IsBackground=true;
t1.IsBackground=true;
t2.IsBackground=true;
t0.Start();
t1.Start();
t2.Start();

We also add a ManualResetEvent object to our class.

C#
public ManualResetEvent m_mre;

We initialize it in our class's constructor.

C#
m_mre = new ManualResetEvent(false);

Now let's look at out SafeWriteThread function

C#
public void SafeWriteThread()
{
	m_mre.Reset();
	WriteThread();
	m_mre.Set();
}

The Reset function sets the state of the event object to non-signaled. This means the event is currently not set. Then we call the original WriteThread function. Actually we could have skipped the Reset step because we had set the state to non-signaled in the ManualResetEvent constructor earlier. Once the WriteThread function returns we call the Set function which will set the state of the event object to signaled. Now the event is said to be set.

Now, let's take a look at out two SafeReadThread functions.

C#
public void SafeReadThread10()
{
	m_mre.WaitOne();
	ReadThread10();
}
C#
public void SafeReadThread20()
{
	m_mre.WaitOne();
	ReadThread20();
}

The WaitOne function will block till the event object's signaled state is set. Thus in our particular scenario, both the SafeReadThreads will block till the event object is signaled. Our SafeWriteThread will set the event only after it has done it's job. Thus we ensure that the reading threads start reading the shared resource only after the writing thread has done it's job. Now when we run the program we get this output which is what we wanted to get.

Voila! Perfecto!

Case 3 - Synchronization [Many WriteThreads - Many ReadThreads]

Now assume we have a situation where we have two WriteThreads. Now the ReadThreads would have to wait till all the WriteThreads have finished their work. In a real scenario, both the WriteThreads would probably be running together, but in our example I've run them in a serialized order where the the second WriteThread starts only after the first one has finished. This is only for ease of simulation. In our case since the second WriteThread starts only after the first WriteThread the ReadThreads need to only wait on the second thread, but as I already said, simply imagine that the two WriteThreads are running in parallel.

We add another ManualResetEvent object for the second thread and also an array of ManualResetEvent objects.

C#
public ManualResetEvent m_mreB;
public ManualResetEvent[] m_mre_array;

Now we add the following initialization code in our constructor

C#
m_mreB = new ManualResetEvent(false);
m_mre_array = new ManualResetEvent[2];
m_mre_array[0]=m_mre;
m_mre_array[1]=m_mreB;

Now lets see how we start the four threads

C#
Thread t0 = new Thread(new ThreadStart(SafeWriteThread));
Thread t0B = new Thread(new ThreadStart(SafeWriteThreadB));
Thread t1 = new Thread(new ThreadStart(SafeReadThread10B));
Thread t2 = new Thread(new ThreadStart(SafeReadThread20B));
t0.IsBackground=true;
t0B.IsBackground=true;
t1.IsBackground=true;
t2.IsBackground=true;
t0.Start();
t0B.Start();
t1.Start();
t2.Start();

As you can see, we now have two StartThreads and two WriteThreads. Lets see their implementations.

C#
public void SafeWriteThread()
{
	m_mre.Reset();
	WriteThread();
	m_mre.Set();
}

As you can see, SafeWriteThread is same as before.

C#
public void SafeWriteThreadB()
{	
	m_mreB.Reset();
	m_mre.WaitOne();
	Thread.Sleep(1000);
	m_x+=3;			
	m_mreB.Set();
}

Well, as you can see we have used another event object for this second WriteThread. For the sake of simulation there is a wait for the first thread to finish its work, but as mentioned before this is not a true representation of the real life state of affairs.

C#
public void SafeReadThread10B()
{
	WaitHandle.WaitAll(m_mre_array);
	ReadThread10();
}

public void SafeReadThread20B()
{
	WaitHandle.WaitAll(m_mre_array);
	ReadThread20();
}

As you can see we have used a function called WaitAll. It's a static member function of the WaitHandle class which is the base class for our ManualResetEvent class. The function takes in an array of WaitHandle objects to which we pass our ManualResetEvent object array. The casting is implicitly done as we are casting to a parent class. What WaitHandle does is this. It will block till each object in the array has been put into a signaled state or in other words till every object in the array has been set. When we run the program this is what we get.

Cool, huh? It worked nice and fine.

AutoResetEvent

There is a very similar class called AutoResetEvent. The difference from the ManualResetEvent class is that the AutoResetEvent is automatically reset to non-signaled after any waiting thread has been released. The best I could figure out for the purpose of this class is this. Lets assume we have several threads waiting for access to an object. We don't want all of them to get access together. So when we are ready to allow access to one thread, we set the event object they are all waiting on. This object will be an AutoResetEvent object. Now one of the threads is released, but the moment that happens, the event is non-signaled automatically. Thus the other threads will continue to wait till the main thread or the thread that is accessing the event object decides to set the event to a signaled state.

I have put together a simple console application to demonstrate this class.

C#
class Class1
{
	AutoResetEvent m_are;
	static void Main(string[] args)
	{
		Class1 class1 = new Class1();			

	}

	Class1()
	{
		m_are = new AutoResetEvent (false);
		Thread t1 = new Thread(new ThreadStart(abc));
		Thread t2 = new Thread(new ThreadStart(xyz));
		t1.Start();
		t2.Start();			
		m_are.Set();
		Thread.Sleep(3000);
		m_are.Set();
	}

	void abc()
	{
		m_are.WaitOne();
		for(int i=0;i<5;i++)
		{
			Thread.Sleep(1000);
			Console.WriteLine("abc abc abc");
		}
	}

	void xyz()
	{
		m_are.WaitOne();
		for(int i=0;i<5;i++)
		{
			Thread.Sleep(1000);
			Console.WriteLine("xyz xyz xyz");
		}
	}
}

When we run the above program we get something like this as output.

abc abc abc
abc abc abc
abc abc abc
xyz xyz xyz
abc abc abc
abc abc abc
xyz xyz xyz
xyz xyz xyz
xyz xyz xyz
xyz xyz xyz

Conclusion

This essay is not a comprehensive one in the sense it does not detail each and every little nuance associated with the thread synchronization event classes. But I do hope it gives you a start from where you can reach out to taller heights or perhaps the expression should be reach down to even more profound depths.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
Nish Nishant is a technology enthusiast from Columbus, Ohio. He has over 20 years of software industry experience in various roles including Chief Technology Officer, Senior Solution Architect, Lead Software Architect, Principal Software Engineer, and Engineering/Architecture Team Leader. Nish is a 14-time recipient of the Microsoft Visual C++ MVP Award.

Nish authored C++/CLI in Action for Manning Publications in 2005, and co-authored Extending MFC Applications with the .NET Framework for Addison Wesley in 2003. In addition, he has over 140 published technology articles on CodeProject.com and another 250+ blog articles on his WordPress blog. Nish is experienced in technology leadership, solution architecture, software architecture, cloud development (AWS and Azure), REST services, software engineering best practices, CI/CD, mentoring, and directing all stages of software development.

Nish's Technology Blog : voidnish.wordpress.com

Comments and Discussions

 
QuestionMy vote of 5 Pin
sam34401-Aug-12 9:19
sam34401-Aug-12 9:19 
QuestionCase 2: is it possible that a shared resource is altered by a writer while readers are accessing it, because the writer is not blocked after all? Pin
wilsonsuryajaya2-May-12 18:14
wilsonsuryajaya2-May-12 18:14 
QuestionSyntax error? Pin
avtuvy22-Feb-11 9:44
avtuvy22-Feb-11 9:44 
AnswerRe: Syntax error? Pin
Nish Nishant22-Feb-11 9:47
sitebuilderNish Nishant22-Feb-11 9:47 
GeneralRe: Syntax error? Pin
avtuvy22-Feb-11 10:15
avtuvy22-Feb-11 10:15 
GeneralRe: Syntax error? Pin
Nish Nishant22-Feb-11 10:32
sitebuilderNish Nishant22-Feb-11 10:32 
AnswerRe: Syntax error? Pin
avtuvy22-Feb-11 10:07
avtuvy22-Feb-11 10:07 
GeneralRe: Syntax error? Pin
Nish Nishant22-Feb-11 10:33
sitebuilderNish Nishant22-Feb-11 10:33 
QuestionDid This Ever Work? Pin
John Pittaway16-Jun-10 13:25
John Pittaway16-Jun-10 13:25 
AnswerRe: Did This Ever Work? Pin
Nish Nishant16-Jun-10 15:19
sitebuilderNish Nishant16-Jun-10 15:19 
It was written nearly 8-9 years ago on .NET 1.0 (possibly the beta). I suppose they didn't check for cross-thread calls those days though they were just as bad back then as they are today. I'll update the project after fixing these issues and re-upload the zip. Thank you.

This was like a blast from the past for me, this is a really old article Smile | :)

AnswerRe: Did This Ever Work? Pin
Nish Nishant16-Jun-10 15:43
sitebuilderNish Nishant16-Jun-10 15:43 
Generaldatabase access using Synchronous Threading Pin
Jan Palmer4-Sep-07 3:03
Jan Palmer4-Sep-07 3:03 
GeneralWow this really helps thanks Pin
oci_Beken14-Aug-07 17:11
oci_Beken14-Aug-07 17:11 
General..thanks Pin
Aeyd M.2-Jun-06 20:25
Aeyd M.2-Jun-06 20:25 
GeneralRe: ..thanks Pin
Christian Klauser12-Aug-06 5:07
Christian Klauser12-Aug-06 5:07 
GeneralRe: ..thanks [modified] Pin
erkan islamovic16-Oct-06 23:19
erkan islamovic16-Oct-06 23:19 
QuestionHow to call a device driver from a VC++ Program? Pin
Kirubanandam27-Aug-05 3:22
Kirubanandam27-Aug-05 3:22 
GeneralAutoResetEvent-example Pin
pat2708818-Aug-04 11:11
pat2708818-Aug-04 11:11 
Generalyes, yes, yes... i'll have what he's having! Pin
Nik Vogiatzis20-Jul-04 15:08
Nik Vogiatzis20-Jul-04 15:08 
GeneralIt seems can not specify the event name Pin
kosmas22-Sep-03 4:22
kosmas22-Sep-03 4:22 
GeneralReaderWriterLock & Monitor Pin
Blake Coverett28-Mar-02 17:16
Blake Coverett28-Mar-02 17:16 
General[Message Deleted] Pin
Nish Nishant28-Mar-02 17:34
sitebuilderNish Nishant28-Mar-02 17:34 
GeneralRe: ReaderWriterLock & Monitor Pin
Blake Coverett28-Mar-02 18:36
Blake Coverett28-Mar-02 18:36 
GeneralRe: ReaderWriterLock & Monitor Pin
Nish Nishant28-Mar-02 18:44
sitebuilderNish Nishant28-Mar-02 18:44 
GeneralRe: ReaderWriterLock & Monitor Pin
Tim Smith30-Mar-02 14:27
Tim Smith30-Mar-02 14:27 

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.