Saturday, February 10, 2007

.Net Threads - 10 Best Practices

I have written earlier (here and here) about Threads. I thought it would be a good idea to list some of the 'best practices' that I have learnt to follow ...

  1. In general, one should use threads for long-running tasks. For example ...
  2.   ThreadStart job = new ThreadStart(ThreadJob);
     Thread thread = new Thread(job);
     thread.Start();
     static void ThreadJob()
     {
         // do something in here...
      }
  3. If using Monitor class to synchronize, ensure that Monitor.Exit is always called
  4.   Monitor.Enter(this);
     try
      {
       y = this.y;
     }
     finally
      {
       Monitor.Exit(this);
     }
  5. Use Monitor.TryEnter to specify a timeout value. This prevents thread gridlock. A more elegant solution is to use TimedLock struct (see References). If using Monitor.TryEnter, ensure that Monitor.Exit is only called if TryEnter was successful, else SynchronizationLockException will be thrown.
  6.   bool lockAcquired = Monitor.TryEnter(this,2000);
     if (lockAcquired)
     {
       try
        {
         this.y = y;
       }
       finally
        {
         Monitor.Exit(this);
       }
     }
     else
      {
       // do something
      }
  7. Never lock a value type. Remember that Monitor.Enter takes an object instance. The value type is therefore boxed into an object type. If the Monitor.Enter is called again on the same value type, a new object will be created, essentially defeating the synchronization effort.
  8. public class TempTest
    {
       private int count = 0;
       public void RunTest()
       {
           System.Threading.Monitor.Enter( count );
           {
               // do something
            }
           System.Threading.Monitor.Exit( count );
       }
    }
  9. Use lock() instead of Monitor.Enter instead. Use of lock() will report #4 above as a compile time error. Also note that 'bool' is not a reference type as required by the lock() statement
  10. public class TempTest
    {
       private int count = 0;
       public void RunTest()
       {
           lock( count ); // will fail at compile time 
           {
               // do something
            }
       }
    }
  11. Use PrivateSyncBlock to synchronize. Never lock 'this' or other public properties. Since an object's SyncBlock is public, it could result in deadlock. In general, it's a very bad idea to rely on locking an object you didn't create and don't know who else might be accessing. Doing so invites deadlock.
  12. class App
    {
       static void Main()
       {
           App a = new App();
           lock ( this )
           {
               a = null;
               GC.Collect();
               GC.WaitForPendingFinalizers();
           }
       }
       ~App()
       {
           // This lock results in a deadlock with the lock in Main
            lock ( this )
           {
               // do something in here...
            }
       }
    }
  13. The safest way is to only lock private objects ...
  14. class App
    {
       private object PrivateSyncBlock = new object();
       ~App()
       {
           lock ( PrivateSyncBlock )
           {
               // do something in here...
            }
       }
    }
  15. Do not synchronize using lock(typeof(InstanceClass)). All classes, weather static or instance, have 1 instance of TypeDescriptor. So if this is locked, it could potentially block other threads, including across application domains (but not processes), resulting in performance and scalability hit. Also 'typeof' results in a big performance hit.
  16. Minimize the number of threads used by your application to the lowest possible. More threads can lead to maintenance and diagnostic nightmare.
  17. Last, but not the least, the more finely grained the locking, the better the performance and scalability.

References

TimedLock struct TimedLock w/ stacktrace TimedLock w/ stacktrace final version http://msdn.microsoft.com/msdnmag/issues/03/01/NET/ http://haacked.com/archive/2006/08/08/ThreadingNeverLockThisRedux.aspx http://msdn.microsoft.com/library/default.asp?url=/archive/en-us/dnaraskdr/html/askgui06032003.asp