Summary

Thread basics using followings: Thread Synchronization using followings: Overview

Architecture of CLR

Architecture of CLR

Threading support is inbuilt under Common Language Runtime provided by Microsoft .NET Framework.

Understanding Threading

  1. Threads are basically lightweight processes responsible for multitasking within a single application.

  2. The base class used for threading is System.Threading

  3. Threads are managed under the Common Language Runtime, programmers don't have to manage any threads explicitly.

  4. Using threading with the combination of components it is recommended to use explicit definition and management of the thread.

  5. Threads are implemented when you have situations in which you want to perform more than one task at a time.

  6. In case of Synchronization. Since you have limited amount of recourses, you may have to restrict the access to the resource to one thread at a time. In this situations, you may implement locking on the threading to overcome the scenarios.

  7. An apartment is a logical container within a process and is used for objects that shares the same thread-access requirement. Object in the apartment can all receive method call from any object in any thread in the apartment. And managed object (object created within CLR) are responsible for thread safety.

  8. Threads can be used under the situation where we must wait for an event such as user input, a read from file, or receipt of data over the network. Freeing the memory to turn it a safe, and it makes our program to appear to run more quickly.

Working with Thread

Create new instance of Thread object. Thread constructor accepts one parameters that is delegate.

MS CLR provides ThreadStart delegate class for starting the thread.

Example:

  1. Thread myThread = new Thread( new ThreadStart(myFunc) );
To run this thread we need following
  1. Thread t1 = new Thread( new ThreadStart(Incrementer) );
To instantiate this we need followings

t1.Start()

Detailed example is as below

  1. namespace Programming_CSharp
  2. {
  3. using System;
  4. using System.Threading;
  5. class Tester
  6. {
  7. static void Main()
  8. {
  9. // make an instance of this class
  10. Tester t = new Tester();
  11. // run outside static Main
  12. t.DoTest();
  13. }
  14. public void DoTest()
  15. {
  16. // create a thread for the Incrementer
  17. // pass in a ThreadStart delegate
  18. // with the address of Incrementer
  19. Thread t1 = new Thread(new ThreadStart(Incrementer));
  20. // create a thread for the Decrementer
  21. // pass in a ThreadStart delegate
  22. // with the address of Decrementer
  23. Thread t2 = new Thread(new ThreadStart(Decrementer));
  24. // start the threads
  25. t1.Start();
  26. t2.Start();
  27. }
  28. // demo function, counts up to 1K
  29. public void Incrementer()
  30. {
  31. for (int i = 0; i < 10; i++)
  32. {
  33. Console.WriteLine("Incrementer: {0}", i);
  34. }
  35. }
  36. // demo function, counts down from 1k
  37. public void Decrementer()
  38. {
  39. for (int i = 10; i >= 0; i--)
  40. {
  41. Console.WriteLine("Decrementer: {0}", i);
  42. }
  43. }
  44. }
  45. }

Output:

C# Threading

Joining Threads

Once thread starts running and in a situation when we need to tell thread to stop processing and wait until a second thread to complete the processing we need to join the 1st thread to the 2nd thread. Use following for the same. This will join the second thread to the 1st.

Example:

t2.Join( )

Suspending Thread

In a situation we some time needs to suspend running thread.

Example:

t2.Sleep(<No of Seconds>)

Killing Thread

Threads have to die after the execution of the process in normal situations, occasionally it is required for the programmer to kill a thread. Threads can be killed using the following.

Example:

t2.Abort()

Advanced Threading

Synchronization

Recollect the discussion we had before in some situations we need to synchronize the running threads, so we can modify the running thread and its resources.
  1. namespace Programming_CSharp
  2. {
  3. using System;
  4. using System.Threading;
  5. class Tester
  6. {
  7. private int counter = 0;
  8. static void Main()
  9. {
  10. // make an instance of this class
  11. Tester t = new Tester();
  12. // run outside static Main
  13. t.DoTest();
  14. }
  15. public void DoTest()
  16. {
  17. Thread t1 = new Thread(new ThreadStart(Incrementer));
  18. t1.IsBackground = true;
  19. t1.Name = "ThreadOne";
  20. t1.Start();
  21. Console.WriteLine("Started thread {0}",
  22. t1.Name);
  23. Thread t2 = new Thread(new ThreadStart(Incrementer));
  24. t2.IsBackground = true;
  25. t2.Name = "ThreadTwo";
  26. t2.Start();
  27. Console.WriteLine("Started thread {0}",
  28. t2.Name);
  29. t1.Join();
  30. t2.Join();
  31. // after all threads end, print a message
  32. Console.WriteLine("All my threads are done.");
  33. }
  34. // demo function, counts up to 1K
  35. public void Incrementer()
  36. {
  37. try
  38. {
  39. while (counter < 10)
  40. {
  41. int temp = counter;
  42. temp++;// increment
  43. // simulate some work in this method
  44. Thread.Sleep(1);
  45. // assign the decremented value
  46. // and display the results
  47. counter = temp;
  48. Console.WriteLine("Thread {0}. Incrementer: {1}", Thread.CurrentThread.Name, counter);
  49. }
  50. }
  51. catch (ThreadInterruptedException)
  52. {
  53. Console.WriteLine("Thread {0} interrupted! Cleaning up...", Thread.CurrentThread.Name);
  54. }
  55. finally
  56. {
  57. Console.WriteLine("Thread {0} Exiting. ", Thread.CurrentThread.Name);
  58. }
  59. }
  60. }
  61. }
Output:

Using Interlock

MS CLR provides synchronization tools and mechanisms. This will allow programmers to place locking overrunning threads.

VB provides a special class called interlocked just for the reason of locking. This consists of two methods Increment and Decrement.

Example:

  1. public void Incrementer()
  2. {
  3. try
  4. {
  5. while (counter < 1000)
  6. {
  7. Interlocked.Increment(ref counter);
  8. // simulate some work in this method
  9. Thread.Sleep(1);
  10. // assign the decremented value
  11. // and display the results
  12. Console.WriteLine("Thread {0}. Incrementer: {1}", Thread.CurrentThread.Name, counter);
  13. }
  14. }
  15. }
Output (excerpts):

Started thread ThreadOne
Started thread ThreadTwo
Thread ThreadOne. Incrementer: 1
Thread ThreadTwo. Incrementer: 2
Thread ThreadOne. Incrementer: 3
Thread ThreadTwo. Incrementer: 4
Thread ThreadOne. Incrementer: 5
Thread ThreadTwo. Incrementer: 6
Thread ThreadOne. Incrementer: 7
Thread ThreadTwo. Incrementer: 8
Thread ThreadOne. Incrementer: 9
Thread ThreadTwo. Incrementer: 10
Thread ThreadOne. Incrementer: 11
Thread ThreadTwo. Incrementer: 12
Thread ThreadOne. Incrementer: 13
Thread ThreadTwo. Incrementer: 14
Thread ThreadOne. Incrementer: 15
Thread ThreadTwo. Incrementer: 16
Thread ThreadOne. Incrementer: 17
Thread ThreadTwo. Incrementer: 18
Thread ThreadOne. Incrementer: 19
Thread ThreadTwo. Incrementer: 20

Using Locks

A lock marks a critical section of the code, provides synchronization to an object.

  1. public void Incrementer()
  2. {
  3. try
  4. {
  5. while (counter < 1000)
  6. {
  7. lock (this)
  8. {
  9. int temp = counter;
  10. temp++;
  11. Thread.Sleep(1);
  12. counter = temp;
  13. }
  14. // assign the decremented value
  15. // and display the results
  16. Console.WriteLine("Thread {0}. Incrementer: {1}", Thread.CurrentThread.Name, counter);
  17. }
  18. }
  19. }
Using Monitor

There are situations where the programmer needs to monitor the running threads for which we can use the followings.

  1. Monitor.Enter(this);
Race Condition and Deadlocks

There are situations when the Process goes for a deadlock situation. Synchronization is little tricky to handle.

Race Conditions

This situation occurs when success of one program depends on the uncontrolled order of execution of certain processes(Two Independent Threads).

We can overcome this situation by using Join() and using Monitor() Wait() etc...

Deadlocks

In the situations when one thread is dependent on the other thread to complete it is sometimes possible that unknowingly one thread can wait for the other to finish, so the second thread can go ahead and run the 2nd process. In a few occasions, each thread may go in a loop to wait for the next thread to complete the processing to start. where both the threads are waiting for each other to complete and none of them are actually doing any processing.