GOTCHA 56 Calling Type.GetType() may not return what you expect


GOTCHA #56 Calling Type.GetType() may not return what you expect

Say you have a static/Shared method and you want to make sure that only one thread at a time can perform that operation. Typically, you use lock/SyncLock on an object to define a critical section. However, this won't help if you are trying to synchronize within a static/Shared method or you're trying to synchronize access to a static/Shared field. Static/Shared members are, by definition, independent from any object of the type. Consider Example 7-8.

Example 7-8. Ineffective synchronization

C# (Synchronizing)

 //Bacteria.cs using System; using System.Threading; namespace SynchOnType {     public class Bacteria     {         private static int bacteriaCount;         private static void IncreaseCount()         {             Console.WriteLine(                 "IncreaseCount called by {0} at {1}",                 AppDomain.GetCurrentThreadId(),                 DateTime.Now.ToLongTimeString());             bacteriaCount++;             Thread.Sleep(2000);             // Used for illustration purpose         }         public Bacteria()         {             lock(this)             {                 IncreaseCount();             }         }     } } //Test.cs using System; using System.Threading; namespace SynchOnType {     class Test     {         private static void Worker()         {             Console.WriteLine("In thread {0}",                 AppDomain.GetCurrentThreadId());             Bacteria aBacteria = new Bacteria();         }         [STAThread]         static void Main(string[] args)         {             Thread thread1 = new Thread(new ThreadStart(Worker));             Thread thread2 = new Thread(new ThreadStart(Worker));             thread1.Start();             thread2.Start();         }     } } 

VB.NET (Synchronizing)

 'Bacteria.vb Imports System.Threading Public Class Bacteria     Private Shared bacteriaCount As Integer     Private Shared Sub IncreaseCount()         Console.WriteLine( _             "IncreaseCount called by {0} at {1}", _             AppDomain.GetCurrentThreadId(), _             DateTime.Now.ToLongTimeString())         bacteriaCount += 1         Thread.Sleep(2000)         ' Used for illustration purpose     End Sub     Public Sub New()         SyncLock Me             IncreaseCount()         End SyncLock     End Sub End Class 'Test.vb Imports System.Threading Module Test     Private Sub Worker()         Console.WriteLine("In thread {0}", _             AppDomain.GetCurrentThreadId())         Dim aBacteria As New Bacteria     End Sub     Public Sub Main()         Dim thread1 As New Thread(AddressOf Worker)         Dim thread2 As New Thread(AddressOf Worker)         thread1.Start()         thread2.Start()     End Sub End Module 

In this example, two threads create an object of the Bacteria class almost at the same time. Within the constructor of Bacteria you call a static/Shared method that requires synchronization, and you lock the instance before invoking the method.

The synchronization in the constructor is fundamentally flawed. The two threads in Test create one Bacteria object each. Each of the threads locks a different instance of Bacteria, and then calls the static method. When the program is executed, the output shown in Figure 7-9 is generated.

Figure 7-9. Output from Example 7-8


As you can see, the two threads execute the IncreaseCount() method at the same time, showing that neither is waiting the Sleep() time (2000 milliseconds) for the other as expected. The synchronization is ineffective.

For two threads to treat a code segment as a critical section, they need to synchronize on the same instance. What is common to both the instances of Bacteria? It's the Bacteria class itself, isn't it? So, why not synchronize on the Bacteria class, i.e., the metadata? The code that does just that is shown in Example 7-9.

Example 7-9. Synchronizing on the metadata

C# (Synchronizing)

         public Bacteria()         {             lock(GetType())             {                 IncreaseCount();             }         } 

VB.NET (Synchronizing)

    Public Sub New()         SyncLock Me.GetType()             IncreaseCount()         End SyncLock     End Sub 

In the constructor of Bacteria you synchronize on the metadata of Bacteria. You obtain this object by calling the GetType() method on the instance. The output after the above change is shown in Figure 7-10.

Figure 7-10. Output after change shown in Example 7-9


The two threads are executing the IncreaseCount() method two seconds apart, indicating that the calls to the method are being synchronized. It works, doesn't it? Are you done? Yep, let's ship it.

Well, sorry, the euphoria is short-lived. Let's write a new class and change the Test class, as in Example 7-10.

Example 7-10. Problem with instance's GetType()

C# (Synchronizing)

 //SpecializedBacteria.cs  using System; namespace SynchOnType {     public class SpecializedBacteria : Bacteria     {     } } //Test.cs using System; using System.Threading; namespace SynchOnType {     class Test     {         private static void Worker1()         {             Console.WriteLine("In thread {0}",                 AppDomain.GetCurrentThreadId());             Bacteria aBacteria = new Bacteria();         }         private static void Worker2()         {             Console.WriteLine("In thread {0}",                 AppDomain.GetCurrentThreadId());             SpecializedBacteria aBacteria                 = new SpecializedBacteria();         }         [STAThread]         static void Main(string[] args)         {             Thread thread1                 = new Thread(new ThreadStart(Worker1));             Thread thread2                 = new Thread(new ThreadStart(Worker2));             thread1.Start();             thread2.Start();         }     } } 

VB.NET (Synchronizing)

 'SpecializedBacteria.vb Public Class SpecializedBacteria     Inherits Bacteria End Class 'Test.vb Imports System.Threading Module Test     Private Sub Worker1()         Console.WriteLine("In thread {0}", _             AppDomain.GetCurrentThreadId())         Dim aBacteria As New Bacteria     End Sub     Private Sub Worker2()         Console.WriteLine("In thread {0}", _             AppDomain.GetCurrentThreadId())         Dim aBacteria As New SpecializedBacteria     End Sub     Public Sub Main()         Dim thread1 As New Thread(AddressOf Worker1)         Dim thread2 As New Thread(AddressOf Worker2)         thread1.Start()         thread2.Start()     End Sub End Module 

In this code, thread1 calls Worker1(), which creates an instance of Bacteria. In the meantime, thread2 calls Worker2(), which creates an instance of SpecializedBacteria. Recall that the constructor of Bacteria is called when you create an instance of SpecializedBacteria. Within the constructor, when you call GetType(), which Type metadata is returned? Because the instance being created is SpecializedBacteria, GetType() returns the Type metadata for SpecializedBacteria, not that of Bacteria. Once again, the two threads end up locking different instances and get into the IncreaseCount() method at the same time, as shown in Figure 7-11.

Figure 7-11. Output from Example 7-10


In this situation, locking on the metadata returned by GetType() has no effect. What can you do to solve this problem? You want to lock the Bacteria class. So, why not do that explicitly? Consider Example 7-11.

Example 7-11. Explicitly locking the right metadata

C# (Synchronizing)

         public Bacteria()         {             lock(typeof(Bacteria))             {                 IncreaseCount();             }         } 

VB.NET (Synchronizing)

     Public Sub New()         SyncLock GetType(Bacteria)             IncreaseCount()         End SyncLock     End Sub 

Here you fetch the Bacteria's Type metadata by using the typeof operator in C# and the GetType() operator in VB.NET. This call returns a consistent metadata object regardless of the instance type. Now, when you execute the code, you get the desired output, as shown in Figure 7-12.

Figure 7-12. Output from Example 7-11


In this specific example, if your only objective is to synchronize the incrementing of the field, use Interlocked.Increment().


Are you done? Is this a solution you can be comfortable with? Well, not fully, but go with it for now while you look at more issues in the next gotcha.

IN A NUTSHELL

If your intent is to fetch the metadata for a specific class, use the typeof/GetType operator instead of the GetType() instance method.

SEE ALSO

Gotcha #20, "Singleton isn't guaranteed process-wide," Gotcha #57, "Locking on globally visible objects is too sweeping," Gotcha #62, "Accessing WinForm controls from arbitrary threads is dangerous," and Gotcha #64, "Raising events lacks thread-safety."



    .NET Gotachas
    .NET Gotachas
    ISBN: N/A
    EAN: N/A
    Year: 2005
    Pages: 126

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