Dec 282009
 

How often have you faced  the irritating not responding screen while the application waits for the data that you requested for. The problem is not with bad code or a slow database, the problem with conventional applications is that all the code is executed on a single thread. i.e. whenever an operation is performed the calling function must wait for it to complete in order to resume execution.

Since the main thread is also the UI thread, the UI stops responding whenever its waiting for the function to return, making the user experience very bad. To avoid this, any time consuming operation should be executed on its own thread rather than the main thread. Lets see a simple example of a method created on another thread.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
        static void Main(string[] args){
            ThreadingApp _testObj = new ThreadingApp();
 
            //Declaring Two Objects for calling the parameterless and parametrized method
            Thread _tObj1 = new Thread(new ParameterizedThreadStart(_testObj.DoSomeWork));
            Thread _tObj2 = new Thread(new ThreadStart(_testObj.DoWork));
 
            //Calling the overloaded method
            _tObj1.Start("CustomParameter");
            //Calling the parameterless method
            _tObj2.Start();
 
        }
 
    class ThreadingApp{
        // Parameterless method executed on anothed
        public void DoWork(){
            Console.WriteLine("Executed On another thread");
        }
 
        public void DoSomeWork(object _name){
            Console.WriteLine("Executed on another thread. Paramter: " + _name);
        }
    }

The ability to pass parameters to methods on other threads is new in .NET 2.0. The delegate introduced for this is the ParametrizedThreadStart delegate which takes in an object parameter. Since its an object type, pretty much anything can be passed to the method. So creating new threads is pretty simple. But a word of caution here – Threading unless used carefully can introduce very hard to detect bugs in your system.

Multiple threads don’t cause any problem till the time each thread runs its own context not depending on shared resources or variables. The issues start when threads start to modify each others variables and access each others resources. Lets see a small example where multiple threads accessing the same variable can lead to unpredictable scenarios. In the below code two variables increment a class level variable and print the values before and after the incrementing. As you can see in the output below, it leads to a chaotic scenario where threads are misreading values and coming in each others way to access the variable.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
    class Program
    {
    int _value = 0;
    static void Main(string[] args){
        Program _prgm = new Program();
        for (int i = 0; i > 5; i++)
        {
            Thread _t1Obj = new Thread(new ThreadStart(_prgm.DoWork1));
            Thread _t2Obj = new Thread(new ThreadStart(_prgm.DoWork2));
 
            _t1Obj.Start();
            _t2Obj.Start();
        }
 
    }
 
    public void DoWork1()
    {
        Console.WriteLine("Thread1 start: " + _value);
        _value++;
        //Introduced a small sleep to bring some entropy in the system
        Thread.Sleep(10);
        Console.WriteLine("Thread1 End: " + _value);
    }
 
    public void DoWork2()
    {
        Console.WriteLine("Thread2 start: " + _value);
        _value++;
        Console.WriteLine("Thread2 End: " + _value);
    }
 
    }

When we see the output, it looks pretty haphazard. Its easy to see the threads are confused about execution and misreading the value of the variable.

The Monitor class can be used to synchronize threads in .NET. How it works is that it puts a lock on the currently executing code making all other threads wait before they can access the variables that current method has locked. The Monitor has two main static methods Monitor.Enter and Monitor.Exit for entering the lock and exiting from it. Apart from this there is also the Monitor.TryEnter which tries to gain ownership of the code block. If its not able to it returns false. This is very important since calling the Monitor.Exit for a block you dont have ownership of, would raise a SynchronizationLockException. However making sure to call Enter and Exit every time would get cumbersome for a developer and there are chances it might be forgotten. Here is where C# comes into rescue with the lock keyword. Enclosing the code within the lock block automatically does all the steps which are mentioned above.

1
2
3
4
5
6
7
8
9
10
11
12
    static readonly object _lockObj = new object();
    public void DoWork1()
    {
        lock (_lockObj)
        {
            Console.WriteLine("Thread1 start: " + _value);
            _value++;
            //Introduced a small sleep to bring some entropy in the system
            Thread.Sleep(10);
            Console.WriteLine("Thread1 End: " + _value);
        }
    }

Enclosing the code within the lock block brings order back to the method execution and we can observe that that each thread waits for the other to complete before attempting to modify the variable. Here is the IL for the above method.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.method public hidebysig instance void  DoWork1() cil managed
{
 
  IL_0000:  ldsfld     object ThreadingApp.Program::_lockObj
  IL_0005:  dup
  IL_0007:  call       void [mscorlib]System.Threading.Monitor::Enter(object)
  .try
  {
    //Our method logic removed for brevity
  }  // end .try
  finally
  {
    IL_0057:  ldloc.0
    IL_0058:  call       void [mscorlib]System.Threading.Monitor::Exit(object)
    IL_005d:  endfinally
  }  // end handler
  IL_005e:  ret
} // end of method Program::DoWork1

A very important point stressed time and again would be to make sure not to expose the locking object outside the class. If this is done, anyone can use the object to lock your methods and not release them while your application threads remain indefinitely waiting for the lock to be freed.

There are many other techniques for thread synchronization in .NET. I will be covering them in upcoming posts.