Recipe18.2.Providing Thread-Safe Access to Class Members


Recipe 18.2. Providing Thread-Safe Access to Class Members

Problem

You need to provide thread-safe access through accessor functions to an internal member variable.

The following NoSafeMemberAccess class shows three methods: ReadNumericField, IncrementNumericField, and ModifyNumericField. While all of these methods access the internal numericField member, the access is currently not safe for multithreaded access:

 public static class NoSafeMemberAccess {     private static int numericField = 1;     public static void IncrementNumericField( )     {         ++numericField;     }     public static void ModifyNumericField(int newValue)     {         numericField = newValue;     }     public static int ReadNumericField( )     {         return (numericField);     } } 

Solution

NoSafeMemberAccess could be used in a multithreaded application, and therefore it must be made thread-safe. Consider what would occur if multiple threads were calling the IncrementNumericField method at the same time. It is possible that two calls could occur to IncrementNumericField while the numericField is updated only once. In order to protect against this, you will modify this class by creating an object that you can lock against in critical sections of the code:

 public static class SaferMemberAccess {     private static int numericField = 1;      private static object syncObj = new object( );     public static void IncrementNumericField( )     {          lock(syncObj)         {             ++numericField;          }     }     public static void ModifyNumericField(int newValue)     {          lock(syncObj)         {             numericField = newValue;  }     }     public static int ReadNumericField( )     {          lock (syncObj)         {             return (numericField);          }     } } 

Using the lock statement on the syncObj object lets you synchronize access to the numericField member. This now makes all three methods safe for multithreaded access.

Discussion

Marking a block of code as a critical section is done using the lock keyword. The lock keyword should not be used on a public type or on an instance out of the control of the program as this can contribute to deadlocks. Examples of this are using the "this" pointer, the type object for a class (typeof(MyClass)), or a string literal ("MyLock"). If you are attempting to protect code in only public static methods, the System.Runtime.CompilerServices.MethodImpl attribute could also be used for this purpose with the MethodImplOption.Synchronized value:

 [MethodImpl (MethodImplOptions.Synchronized)] public static void MySynchronizedMethod() { } 

There is a problem with synchronization using an object such as syncObj in the SaferMemberAccess example. If you lock an object or type that can be accessed by other objects within the application, other objects may also attempt to lock this same object. This will manifest itself in poorly written code that locks itself, such as the following code:

 public class DeadLock {     public void Method1( )     {         lock(this)         {             // Do something.         }     } } 

When Method1 is called, it locks the current DeadLock object. Unfortunately, any object that has access to the DeadLock class may also lock it. This is shown here:

 using System; using System.Threading; public class AnotherCls {     public void DoSomething( )     {         DeadLock deadLock = new DeadLock( );         lock(deadLock)         {             Thread thread = new Thread(deadLock.Method1);             thread.Start( );             // Do some time-consuming task here.         }     } } 

The DoSomething method obtains a lock on the deadLock object and then attempts to call the Method1 method of the deadLock object on another thread, after which a very long task is executed. While the long task is executing, the lock on the deadLock object prevents Method1 from being called on the other thread. Only when this long task ends, and execution exits the critical section of the DoSomething method, will the Method1 method be able to acquire a lock on the this object. As you can see, this can become a major headache to track down in a much larger application.

Jeffrey Richter has come up with a relatively simple method to remedy this situation, which he details quite clearly in the article "Safe Thread Synchronization" in the January 2003 issue of MSDN Magazine. His solution is to create a private field within the class on which to synchronize. Only the object itself can acquire this private field; no outside object or type may acquire it. This solution is also now the recommended practice in the MSDN documentation for the lock keyword. The DeadLock class can be rewritten, as follows, to fix this problem:

 public class DeadLock {     private object syncObj = new object( );     public void Method1( )     {         lock(syncObj)         {             // Do something.         }     } } 

Now in the DeadLock class, you are locking on the internal syncObj, while the DoSomething method locks on the DeadLock class instance. This resolves the deadlock condition, but the DoSomething method still should not lock on a public type. Therefore, change the AnotherCls class like so:

 public class AnotherCls {     private object deadLockSyncObj = new object( );     public void DoSomething( )     {         DeadLock deadLock = new DeadLock( );         lock(deadLockSyncObj)         {             Thread thread = new Thread(deadLock.Method1);             thread.Start( );             // Do some time-consuming task here.         }     } } 

Now the AnotherCls class has an object of its own to protect access to the DeadLock class instance in DoSomething instead of locking on the public type.

To clean up your code, you should stop locking any objects or types except for the synchronization objects that are private to your type or object, such as the syncObj in the fixed DeadLock class. This recipe makes use of this pattern by creating a static syncObj object within the SaferMemberAccess class. The IncrementNumericField, ModifyNumericField, and ReadNumericField methods use this syncObj to synchronize access to the numericField field. Note that if you do not need a lock while the numericField is being read in the ReadNumericField method, you can remove this lock block and simply return the value contained in the numericField field.

Minimizing the number of critical sections within your code can significantly improve performance. Use what you need to secure resource access, but no more.


If you require more control over locking and unlocking of critical sections, you might want to try using the overloaded static Monitor.TryEnter methods. These methods allow more flexibility by introducing a timeout value. The lock keyword will attempt to acquire a lock on a critical section indefinitely. However, with the TRyEnter method, you can specify a timeout value in milliseconds (as an integer) or as a TimeSpan structure. The tryEnter methods return true if a lock was acquired and false if it was not. Note that the overload of the tryEnter method that accepts only a single parameter does not block for any amount of time. This method returns immediately, regardless of whether the lock was acquired.

The updated class using the Monitor methods is shown in Example 18-1.

Example 18-1. Using Monitor methods

 using System; using System.Threading; public static class MonitorMethodAccess {     private static int numericField = 1;     private static object syncObj = new object( );     public static object SyncRoot     {         get { return syncObj; }     }     public static void IncrementNumericField( )     {         if (Monitor.TryEnter(syncObj, 250))         {             try             {                 ++numericField;             }             finally             {                 Monitor.Exit(syncObj);             }         }     }     public static void ModifyNumericField(int newValue)     {         if (Monitor.TryEnter(syncObj, 250))         {             try             {                 numericField = newValue;             }             finally             {                 Monitor.Exit(syncObj);             }         }     }     public static int ReadNumericField( )     {         if (Monitor.TryEnter(syncObj, 250))         {             try             {                 return (numericField);             }             finally             {                 Monitor.Exit(syncObj);             }         }         return (-1);     } } 

Note that with the tryEnter methods, you should always check to see whether the lock was in fact acquired. If not, your code should wait and try again or return to the caller.

You might think at this point that all of the methods are thread-safe. Individually, they are, but what if you are trying to call them and you expect synchronized access between two of the methods? If ModifyNumericField and ReadNumericField are used one after the other by Class 1 on Thread 1 at the same time Class 2 is using these methods on Thread 2, locking or Monitor calls will not prevent Class 2 from modifying the value before Thread 1 reads it. Here is a series of actions that demonstrates this:


Class 1 Thread 1

Calls ModifyNumericField with 10


Class 2 Thread 2

Calls ModifyNumericField with 15


Class 1 Thread 1

Calls ReadNumericField and gets 15, not 10


Class 2 Thread 2

Calls ReadNumericField and gets 15, which it expected

In order to solve this problem of synchronizing reads and writes, the calling class needs to manage the interaction. The external class can accomplish this by using the Monitor class to establish a lock on the exposed synchronization object SyncRoot from MonitorMethodAccess, as shown here:

 int num = 0; if(Monitor.TryEnter(MonitorMethodAccess.SyncRoot,250)) {     MonitorMethodAccess.ModifyNumericField(10);     num = MonitorMethodAccess.ReadNumericField();     Monitor.Exit(MonitorMethodAccess.SyncRoot); } Console.WriteLine(num); 

See Also

See the "Lock Statement," "Thread Class," and "Monitor Class" topics in the MSDN documentation; see the "Safe Thread Synchronization" article in the January 2003 issue of MSDN Magazine.



C# Cookbook
Secure Programming Cookbook for C and C++: Recipes for Cryptography, Authentication, Input Validation & More
ISBN: 0596003943
EAN: 2147483647
Year: 2004
Pages: 424

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net