Tuesday 24 September 2013

Sequencing thread access with ManualResetEvent and AutoResetEvent

Threads! tiny things can make a very complex and sophisticated article like clothe and many more. But when not handled with care and knowledge, can spoil all your work and sometimes almost impossible to separate out.
Yes this is the best simulation of nature of .Net Threads. We call thread a light-weight process in terms of many aspects. And this light-weight thing can make very sophisticated software applications, but when used inappropriately can result in hell of bugs in your application. A multi-threaded application is hard to debug and understand. Yes, but most of the sophisticated applications cannot be written without threading.

In this post I am covering ManualResetEvent and AutoResetEvent.

ManualResetEvent and AutoResetEvent are basically used for sequencing the thread access in a multithreaded environment, although can also be used for implementing exclusive access to a shared resource.
Here is an example how to use AutoResetEvent for mutual exclusion

class Program
{
    static void Main(string[] args)
    {
        ThreadingTest.StartTasks();
    }



public class ThreadingTest
{
    private static AutoResetEvent are = new AutoResetEvent(false);
    public static void StartTasks()
    {
        Thread[] threads = new Thread[15];
        for (int i = 0; i < 15; i++)
        {


            threads[i] = new Thread(new ThreadStart(WriteToFile));

            threads[i].Name = i.ToString();


            threads[i].Start();
        }


        Console.WriteLine("Press enter key to start.");
        Console.ReadLine();
        are.Set(); 
        for (int i = 0; i < 15; i++)
        {


            threads[i].Join();
        }

        Console.ReadKey();
    }
    public static void WriteToFile()
    {


        are.WaitOne();

        Console.WriteLine("Thread " + Thread.CurrentThread.Name + "Entered");
        File.AppendAllText(@"d:\test.txt", "This text can be inserted at any position.");
        Thread.Sleep(1000);
        Console.WriteLine("Thread " + Thread.CurrentThread.Name + "Exited");
        are.Set();


    }
}


But there are two points to consider here.
  1. For mutual exclusion lock or monitor is the best and easy solution. If AutoResetEvent resets automatically with WaitOne call but if both the operations WaitOne and Reset does not execute atomically, your application logic could be in great jeopardy.
  2. ManualResetEvent cannot be used for mutual exclusion in this situation as it does not reset automatically.

Now consider the above example in which we are writing to a file. In the above example the text has to be written in a sequential order to the file, what is our option then to implement that? Of course, the solution is ManulaResetEvent or AutoResetEvent. Now, see the code snippet below that explains the situation:

public class ThreadingTest
{   
    static ManualResetEvent[] mre = new ManualResetEvent[3];
    public static void StartTasks()
    {        Thread[] threads = new Thread[3];
        for (int i = 0; i < 3; i++)
        {
            mre[i] =
new ManualResetEvent(false);
        }

        threads[0] =
new Thread(new ThreadStart(Task1));
        threads[1] = new Thread(new ThreadStart(Task2));
        threads[2] = new Thread(new ThreadStart(Task3));

        for (int i = 0; i < 3; i++)
        {
            threads[i].Start();
        }
        // Signal the first event
        mre[0].Set();
    }
    public static void Task1()
    {
        // Do some work here.
        mre[0].WaitOne();
        WriteToFile(
"This is the first text that should be written in the file.");
        // Signal the next event so that other thread can write to file.
        mre[1].Set();
    }

    public static void Task2()
    {
        // Do some work here.
        mre[1].WaitOne();
        WriteToFile(
"This is the second text that should be written in the file.");
        // Signal the next event so that other thread can write to file.
        mre[2].Set();
    }

   
public static void Task3()
    {
        // Do some work here.       
        WaitHandle.WaitAll(mre);
        WriteToFile("This is the last text that should be written in the file.");
    }

   
public static void WriteToFile(string text)
    {
       
File.AppendAllText(@"d:\test.txt", text + Environment.NewLine);
    }
}

If you observe in the above code the three threads are doing their respective tasks but the access to file write is sequential. And that sequence is controlled by ResetEvents. In this situation we can use either ManualResetEvent or AutoResetEvent.
However there are situations where one is preferred over the other. You can read that in my next article ManualResetEvent or AutoResetEvent?.

No comments:

Post a Comment