Sunday, February 28, 2010

Exception Handling Best Practices

 

  • Serialize exceptions, so that they are available across app domains.
  • As far as possible, let the exception be in the same assembly that throws it.
  • Use at least the 3 common constructors that Exception class provides.
  • Use pre-defined exceptions if possible instead of custom exceptions.
  • Return null instead of exception for common errors
  • Categorize exceptions in a hierarchy based on modules.
  • Include localized description string for exception.
  • Provide Exception properties for programmatic access.
  • Do not catch generic exceptions when specific exceptions can be caught.
  • Check for ErrorCode property in COMException for HResult value.
  • Make COM objects implement IErrorInfo, so that they can be access in .NET

Tuesday, February 23, 2010

Race condition and Deadlock

 

A race condition occurs when two threads access a shared variable at the same time. The first thread reads the variable, and the second thread reads the same value from the variable. Then the first thread and second thread perform their operations on the value, and they race to see which thread can write the value last to the shared variable. The value of the thread that writes its value last is preserved, because the thread is writing over the value that the previous thread wrote.

The typical solution to a race condition is to ensure that your program has exclusive rights to something while it's manipulating it, such as a file, device, object, or variable. The process of gaining an exclusive right to something is called locking.

A deadlock occurs when two threads each lock a different variable at the same time and then try to lock the variable that the other thread already locked. As a result, each thread stops executing and waits for the other thread to release the variable. Because each thread is holding the variable that the other thread wants, nothing occurs, and the threads remain deadlocked.

Deadlock Prevention

1. Remove mutual exclusion – no process should have exclusive access to a resource
2. Process should acquire all required resources before starting
3. By releasing resources after a certain amount of time
4. By assigning precedence to resources, so process an only try to acquire lower precedence resource

Managed Threading Best Practices

 

We use threading to increase responsiveness of our application and to do multiple tasks simultaneously(typically IO and CPU bound tasks).

  • Don't use Thread.Abort
    to terminate other threads. Calling Abort on another thread is akin to throwing an exception on that thread, without knowing what point that thread has reached in its processing.
  • Don't use Thread.Suspend and Thread.Resume
    to synchronize the activities of multiple threads. Do use Mutex, ManualResetEvent, AutoResetEvent, and Monitor.
  • Don't control the execution of worker threads from your main program (using events, for example).
    Instead, design your program so that worker threads are responsible for waiting until work is available, executing it, and notifying other parts of your program when finished. If your worker threads do not block, consider using thread pool threads. Monitor..::.PulseAll is useful in situations where worker threads block.
  • Don't use types as lock objects.
    That is, avoid code such as lock(typeof(X)), or the use of Monitor..::.Enter with Type objects. For a given type, there is only one instance of System..::.Type per application domain. If the type you take a lock on is public, code other than your own can take locks on it, leading to deadlocks.
  • Use caution when locking on instances, for example lock(this) in C#. If other code in your application, external to the type, takes a lock on the object, deadlocks could occur.
  • Do ensure that a thread that has entered a monitor always leaves that monitor, even if an exception occurs while the thread is in the monitor. The C# lock statement provide this behavior automatically, employing a finally block to ensure that Monitor..::.Exit is called. If you cannot ensure that Exit will be called, consider changing your design to use Mutex. A mutex is automatically released when the thread that currently owns it terminates.
  • Do use multiple threads for tasks that require different resources, and avoid assigning multiple threads to a single resource. For example, any task involving I/O benefits from having its own thread, because that thread will block during I/O operations and thus allow other threads to execute. User input is another resource that benefits from a dedicated thread. On a single-processor computer, a task that involves intensive computation coexists with user input and with tasks that involve I/O, but multiple computation-intensive tasks contend with each other.
  • Consider using methods of the Interlocked class for simple state changes,
    instead of using the lock statement. The lock statement is a good general-purpose tool, but the Interlocked class provides better performance for updates that must be atomic. Internally, it executes a single lock prefix if there is no contention. In code reviews, watch for code like that shown in the following examples.
  • Avoid the need for synchronization, if possible.
    This is especially true for heavily used code. For example, an algorithm might be adjusted to tolerate a race condition rather than eliminate it. Unnecessary synchronization decreases performance and creates the possibility of deadlocks and race conditions.
  • Make static data thread safe by default.
  • Do not make instance data thread safe by default.
    Adding locks to create thread-safe code decreases performance, increases lock contention, and creates the possibility for deadlocks 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.
  • 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 of threading bugs. Consider using a design pattern that encapsulates data into instances that are not shared across requests. Furthermore, if static data are synchronized, calls between static methods that alter state can result in deadlocks or redundant synchronization, adversely affecting performance.