Captured variables, or- What did I just code?

Microsoft .Net and Visual Studio (especially the later versions) offer many automatic tools and code sugar that allows us to write code faster and safer.

But, every once in a while it is recommended to take a peek under the hood and see what the compiler had in mind, and how far it went from what we originally meant in our source code.

I do that using tools like the .Net Reflector, now a paid product and yet worth its price, at least for me, though there are other alternatives like the ILSpy.

Here’s an example-

I guess you’re already familiar with Lambda expressions. There are situations, though, where using Lambda expressions without caution might create severe bugs and/or performance hits.

Please see the following code, which you can copy directly to your Visual Studio to a fresh C# Console Application and test:

public static int CalculateFactorial(int number)
{
  int result = 1;
  for (int i = 2; i <= number; i++)
  {
    result *= i;
  }
  return result;
}

public static void Test()
{
  for (int counter = 1; counter <= 10; counter++)
  {
    ThreadPool.QueueUserWorkItem(state => 
      Console.WriteLine("Factorial of {0} is {1}", 
      counter, CalculateFactorial(counter)));
  }
}

It looks like we’ve created an efficient and somewhat elegant piece of code, aimed to calculate factorials of numbers between 1 and 10 while making the most of our computer processors, as we’re breaking these calculations to different threads.

So, where’s the catch?

Let’s open the .Net reflector and see the compiled code for the “Test” method:

public static void Test()
{
  for (int counter = 2; counter <= 10; counter++)
  {
    ThreadPool.QueueUserWorkItem(delegate(object state)
    {
      Console.WriteLine("Factorial of {0} is {1}", 
	counter, CalculateFactorial(counter));
    });
  }
}

OK, so we just found out that Lambda expressions are actually compiled into .Net
anonymous delegates. Lambda expressions are only code sugar and do not exist in the compiled CIL…

Big deal. Still, where’s the catch??

The first catch is that .Net reflector is smart enough not to display the actual code, but more of a sweetened version of the code.

In order to see the actual code we either need to view it in raw CIL, or change the reflector optimization mode to .Net 1.0.

So, here’s the same compiled CIL code once again, in decompiled to C# using in .Net 1.0 mode:

public static void Test()
{  
  WaitCallback CS$<>9__CachedAnonymousMethodDelegate1=null;  
  <>c__DisplayClass2 CS$<>8__locals3 = new <>c__DisplayClass2();  
  CS$<>8__locals3.counter = 2;  
  while (CS$<>8__locals3.counter <= 10)  
  {    
    if (CS$<>9__CachedAnonymousMethodDelegate1 == null)    
    {        
      CS$<>9__CachedAnonymousMethodDelegate1 =           
        new WaitCallback(CS$<>8__locals3.<Test>b__0);    
    }        
    ThreadPool.QueueUserWorkItem(
      CS$<>9__CachedAnonymousMethodDelegate1);        
    CS$<>8__locals3.counter++;  
  }
}

[CompilerGenerated]
private sealed class <>c__DisplayClass2
{  
  // Fields  
  public int counter;   
  // Methods  
  public <>c__DisplayClass2() 
  {
  }  
  public void <Test>b__0(object state)  
  {    
    Console.WriteLine("Factorial of {0} is {1}", 
      this.counter,       
      CalculateFactorial(this.counter));  
  }
}

Browsing through the above generated code, we can now learn that anonymous delegates
are also somewhat of “virtual” code elements that compile into something else, or- some more syntactic sugar!
Well, what the compiler actually does is to create a nested class with some kind of a garbled name that we cannot compile for our own classes
(this is in order to avoid code collisions with our own code).

This generated class holds a method that has the implementation of our original “anonymous
delegate“, to which the compiler directs the ThreadPool.QueueUserWorkItem delegate
parameter to run within an available thread.

We were writing .Net v1.0/2.0 code all along and we didn’t even know that!…

Now that we understand that, let’s see what happened to our “counter” variable from the original Lambda version:

In order to support it, the compiler added a public field to the nested class holding the implementation
method, by the name of “counter”. This counter field is being incremented once for
each loop cycle, just after the internal <Test>b__0 method is assigned for another
ThreadPool thread!

This means that the counter, being a shared resource, will probably make it all the
way up to 10 before the first thread even starts to run, thus causing all of these
threads to calculate the factorial of a 10, instead what we originally intended them to do:
calculate the factorials of every number between 1 and 10…

Such a problem is called: “Captured variables” as we captured the local variable within other threads, thus casuing it become a shared resource.

It and can be avoided by passing the counter as “state” parameter into the delegate, this way every thread owns its own private copy of the counter for each thread:

public static void Test()
{
  for (int counter = 1; counter <= 10; counter++)
  {
    ThreadPool.QueueUserWorkItem(state =>
      Console.WriteLine("Factorial of {0} is {1}",
      (int)state,
      CalculateFactorial((int)state)), counter);
  }
}

You can copy the above code to your Visual Studio and see that it now prints the correct results.

You can also go back to .Net Reflector to see what was built now by the compiler. It might just surprise you…

Advertisements

Posted on April 7, 2012, in Uncategorized and tagged , , , , , , , , , . Bookmark the permalink. Leave a comment.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: