Click here to Skip to main content
15,881,866 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 377.6K   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

 
GeneralRe: ReaderWriterLock & Monitor Pin
Tim Smith30-Mar-02 16:01
Tim Smith30-Mar-02 16:01 
GeneralRe: ReaderWriterLock & Monitor Pin
William E. Kempf1-Apr-02 4:24
William E. Kempf1-Apr-02 4:24 
GeneralRe: ReaderWriterLock & Monitor Pin
Tim Smith1-Apr-02 11:04
Tim Smith1-Apr-02 11:04 
GeneralRe: ReaderWriterLock & Monitor Pin
Joao Vaz1-Apr-02 11:24
Joao Vaz1-Apr-02 11:24 
GeneralRe: ReaderWriterLock & Monitor Pin
Tim Smith1-Apr-02 11:59
Tim Smith1-Apr-02 11:59 
GeneralRe: ReaderWriterLock & Monitor Pin
Tim Smith1-Apr-02 12:01
Tim Smith1-Apr-02 12:01 
GeneralRe: ReaderWriterLock & Monitor Pin
Joao Vaz1-Apr-02 22:08
Joao Vaz1-Apr-02 22:08 
GeneralRe: ReaderWriterLock & Monitor Pin
William E. Kempf1-Apr-02 12:18
William E. Kempf1-Apr-02 12:18 
You are the one who brought the argument to this article, not me. I would have been perfectly happy to continue and finish this argument in Herb's article. However, you are the one who came here and started harassing me.

Sorry, I shouldn't have responded to two posts at once, because even with my explanation things got confused. The two posts were both in *this* thread, not in this thread and in Herb's article.

I don't have to pass everything I say and do by you. Even the suggestion is insulting. So before you go around pointing fingers with regards to who is being inflammatory or condescending, you really should review you messages again and then decide who started.

I never said you needed to pass anything by me. I didn't even suggest that. I also don't see anything in the quote you made that indicates in any way that I was inflamatory. Disagreeing with you is not inflamatory.

Obviously, however, you've felt slighted in some way by what I've said. So, I'll offer you an appology. I never meant to do so.

You said events only work by "pure luck". This is all I have been arguing with you about as far as events. If you wish to argue about the problems of standardizing to the least common denominator, that is a different issue.

*sigh* This quote is taken out of context. Unfortunately, the post in which I made this statement was not worded very well, so combined with taking it out of context this gives a totally different meaning that what I intended. Events obviously don't always work by "pure luck". The point I was trying to make is that in a LOT of code, the events are working by "pure luck". Your attempt at a solution before shows this. You tested and found no bugs in the code, despite the fact that a bug did exist. You can't rely on testing, or even on evidence of a system running with out errors for any length of time, to be an indication of MT correct code, as most of us know from first hand experience Frown | :(

I have done everything you have asked as far as providing examples of event implementations. All I have asked from you is proof of your statements and time and time again, references you make actually refute the statements you have made. Time any time again you have pointed to the Schmidt article as proof that events don't work. The funny part is that Schmidt actually RECOMMENDS using events.

As I've said again and again, the statements I've made can't be proven conclusively (actually, I expect they can be through complex mathematical proofs, but I don't have the ability to do so). But that fact doesn't change anything in what I've claimed.

And I don't really think you've read the Schmidt article carefully enough. You keep making claims that aren't true. He never "recommends" using events, and he asserts quite clearly that CVs can't be implemented using events alone with out SignalObjectAndWait().

William E. Kempf
GeneralRe: ReaderWriterLock & Monitor Pin
Tim Smith1-Apr-02 12:40
Tim Smith1-Apr-02 12:40 
GeneralRe: I am sorry... Pin
Tim Smith1-Apr-02 12:31
Tim Smith1-Apr-02 12:31 
GeneralRe: I am sorry... Pin
William E. Kempf1-Apr-02 12:35
William E. Kempf1-Apr-02 12:35 
GeneralRe: I am sorry... Pin
Joao Vaz1-Apr-02 22:33
Joao Vaz1-Apr-02 22:33 
GeneralRe: I am sorry... Pin
Tim Smith2-Apr-02 2:05
Tim Smith2-Apr-02 2:05 
GeneralRe: I am sorry... Pin
Joao Vaz2-Apr-02 2:46
Joao Vaz2-Apr-02 2:46 
GeneralRe: I am sorry... Pin
William E. Kempf2-Apr-02 8:19
William E. Kempf2-Apr-02 8:19 
GeneralRe: I am sorry... Pin
Tim Smith2-Apr-02 9:53
Tim Smith2-Apr-02 9:53 
GeneralRe: I am sorry... Pin
William E. Kempf2-Apr-02 9:56
William E. Kempf2-Apr-02 9:56 
GeneralRe: I am sorry... Pin
Tim Smith2-Apr-02 10:06
Tim Smith2-Apr-02 10:06 
GeneralRe: I am sorry... Pin
Joao Vaz2-Apr-02 11:09
Joao Vaz2-Apr-02 11:09 
GeneralBaffling! Pin
Nish Nishant28-Mar-02 5:43
sitebuilderNish Nishant28-Mar-02 5:43 
GeneralRe: Baffling! [solved] Pin
Nish Nishant28-Mar-02 13:57
sitebuilderNish Nishant28-Mar-02 13:57 
GeneralRe: Baffling! [solved] Pin
28-Mar-02 14:01
suss28-Mar-02 14:01 
GeneralRe: Baffling! [solved] Pin
Nish Nishant28-Mar-02 14:43
sitebuilderNish Nishant28-Mar-02 14:43 
GeneralRe: Baffling! [solved] Pin
Kannan Kalyanaraman28-Mar-02 20:05
Kannan Kalyanaraman28-Mar-02 20:05 
GeneralRe: Baffling! [solved] Pin
Nish Nishant28-Mar-02 20:39
sitebuilderNish Nishant28-Mar-02 20:39 

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.