Threading Design Guidelines

The following rules outline the design guidelines for implementing threading:

  • Avoid providing static methods that alter static state. In common server scenarios, static state is shared across requests, which means multiple threads can execute that code at the same time. This opens up the possibility for threading bugs. Consider using a design pattern that encapsulates data into instances that are not shared across requests.

  • Static state must be thread safe.

  • Instance state does not need to be thread safe. By default, class libraries should not be thread safe. Adding locks to create thread-safe code decreases performance, increases lock contention, and creates the possibility for deadlock bugs to occur. In common application models, only one thread at a time executes user code, which minimizes the need for thread safety. For this reason, the .NET Framework class libraries are not thread safe by default. In cases where you want to provide a thread-safe version, provide a static Synchronized method that returns a thread-safe instance of a type. For an example, see the System.Collections.ArrayList.Synchronized Method and the System.Collections.ArrayList.IsSynchronized Method.

  • Design your library with consideration for the stress of running in a server scenario. Avoid taking locks whenever possible.

  • Be aware of method calls in locked sections. Deadlocks can result when a static method in class A calls static methods in class B and vice versa. If A and B both synchronize their static methods, this will cause a deadlock. You might discover this deadlock only under heavy threading stress.

  • Performance issues can result when a static method in class A calls a static method in class A. If these methods are not factored correctly, performance will suffer because there will be a large amount of redundant synchronization. Excessive use of fine-grained synchronization might negatively impact performance. In addition, it might have a significant negative impact on scalability.

  • Be aware of issues with the lock statement (SyncLock in Visual Basic). It is tempting to use the lock statement to solve all threading problems. However, the System.Threading.Interlocked Class is superior for updates that must be atomic. It executes a single lock prefix if there is no contention. In a code review, you should watch out for instances like the one shown in the following example.

    SyncLock Me
       myField += 1
    End SyncLock
    [C#]
    lock(this) 
    {
       myField++;
    }
    

    If you replace the previous example with the following one, you will improve performance.

    System.Threading.Interlocked.Increment(myField)
    [C#]
    System.Threading.Interlocked.Increment(myField);
    

    Another example is to update an object type variable only if it is null (Nothing in Visual Basic). You can use the following code to update the variable and make the code thread safe.

    If x Is Nothing Then
       SyncLock Me
          If x Is Nothing Then
             x = y
          End If
       End SyncLock
    End If
    [C#]
    if (x == null)
    {
       lock (this)
       {
          if (x == null)
          {
             x = y;
          }
       }
    }
    

    You can improve the performance of the previous sample by replacing it with the following code.

    System.Threading.Interlocked.CompareExchange(x, y, Nothing)
    [C#]
    System.Threading.Interlocked.CompareExchange(ref x, y, null);
    
  • Avoid the need for synchronization if possible. For high traffic pathways, it is best to avoid synchronization. Sometimes the algorithm can be adjusted to tolerate race conditions rather than eliminate them.

See Also

Design Guidelines for Class Library Developers | Visual Basic Language Changes | System.Threading Namespace