Software development industry analysis by Larry O'Brien, the former editor of Software Development and Computer Language
Sunday, August 20, 2006

Before talking about value and reference types and delegates/closures, let me point out that Monitor-based locking has a fundamental flaw when working with delegates (the same flaw that it has with virtual method calls on objects): you can never safely call a delegate inside a lock-block that is holding a lock on a resource that is conceivably reachable by the delegate (hint of a return to the issue of "closing over" local variables). This is because it's possible that the delegate will start a thread that attempts to lock the same resource that you've locked, an attempt which will result in deadlock.

 In the following program, the Library is running in its own thread, spinning on the lines 34-47, which call back to myDelegate() on line 42. This callback is in the middle of a critical section, lines 38-44, which have locked this.

Now consider the function LockAndTalk(), lines 79-97. It, too, has a critical section, beginning at line 86. It attempts to lock a resource, in this case, the Library which it is using. If it succeeds, we consider the callback to be a success. And, if the Library's delegate callback is set to Client's FineCall(), everything works fine, because the Line 86 lock of the Library and the line 38 lock of the same Library occur within the same thread.

However, consider what happens when the Library's delegate callback is set to BadCall() (lines 58-69), which looks a lot like FineCall(), but happens to call the function LockAndTalk() in the context of a separate thread. Now, the locks at line 86 and 38 are called within separate threads and deadlock.

As soon as you enclose a callback (virtual method or delegate) within the language-promoted Monitor-based strategies of C# ( lock ) or Java ( synchronized ), you've shot yourself in the foot.

I'm late for my Frisbee game, so I guess I'll have to leave an example that produces a deadlock via a captured outer variable for another day...

 

    1 //Never call a delegate inside a lock

    2 using System;

    3 using System.Threading;

    4 using System.Collections;

    5 

    6 delegate void VoidDelegate();

    7 

    8 class Library

    9 {

   10     VoidDelegate myDelegate;

   11     public VoidDelegate MyDelegate

   12     {

   13         set { myDelegate = value; }

   14     }

   15 

   16     Thread t;

   17 

   18     public void Run()

   19     {

   20         ThreadStart ts = new ThreadStart(ThreadCaller);

   21         t = new Thread(ts);

   22         t.Name = "Library thread";

   23         t.Start();

   24     }

   25 

   26     public void Stop()

   27     {

   28         t.Abort();

   29         t.Join();

   30     }

   31 

   32     void ThreadCaller()

   33     {

   34         while (true)

   35         {

   36             Console.WriteLine(Thread.CurrentThread.Name +

   37               " asking for lock on " + this.GetHashCode());

   38             lock (this)

   39             {

   40                 Console.WriteLine(Thread.CurrentThread.Name +

   41                   " acquired lock");

   42                 myDelegate();

   43                 Thread.Sleep(1000);

   44             }

   45             Console.WriteLine(Thread.CurrentThread.Name +

   46               " released lock");

   47         }

   48     }

   49 }

   50 

   51 class Client

   52 {

   53     internal void FineCall()

   54     {

   55         LockAndTalk();

   56     }

   57 

   58     internal void BadCall()

   59     {

   60         ThreadStart ts = new ThreadStart(LockAndTalk);

   61         Thread t = new Thread(ts);

   62         t.Name = "BadClient";

   63         t.IsBackground = true;

   64         t.Start();

   65         while (callDone == false)

   66         {

   67             Thread.Sleep(1000);

   68         }

   69     }

   70 

   71     Library l;

   72     public Library Library

   73     {

   74         set { l = value; }

   75     }

   76 

   77     protected bool callDone = false;

   78 

   79     public void LockAndTalk()

   80     {

   81         callDone = false;

   82         while (callDone == false)

   83         {

   84             Console.WriteLine(this.GetType() +

   85               " asking for lock on " + l.GetHashCode());

   86             lock (l)

   87             {

   88                 Console.WriteLine(Thread.CurrentThread.Name +

   89                   " acquired lock");

   90                 Console.WriteLine("Delegate executed");

   91                 Thread.Sleep(1000);

   92                 callDone = true;

   93             }

   94         }

   95         Console.WriteLine(Thread.CurrentThread.Name +

   96           " released lock");

   97     }

   98 }

   99 

  100 

  101 class TestingClass

  102 {

  103     static Library l;

  104     TestingClass(Client c, VoidDelegate myDelegate)

  105     {

  106         l = new Library();

  107         c.Library = l;

  108         l.MyDelegate = myDelegate;

  109         l.Run();

  110 

  111         Thread.Sleep(10000);

  112         Console.WriteLine("Ending test now...");

  113         l.Stop();

  114     }

  115 

  116     public static void Main()

  117     {

  118         Client c = new Client();

  119         new TestingClass(c, c.FineCall);

  120         Console.WriteLine("Okay, that went fine.");

  121 

  122         Client c2 = new Client();

  123         new TestingClass(c2, c2.BadCall);

  124 

  125     }

  126 }

Search
About Larry...
Flickr photostream
Subscribe: RSS 2.0 Atom 1.0
Popular Articles
Programming Sabre with Java, C#, and XML
Genetic Programming in C#
15 Exercises To Know A Programming Language
Top 10 Things I've Learned About Computers From the Movies and Any Episode of "24"
Recently Published Articles
HI
KonaKoder
Categories
Archive
Admin Login
Sign In
Toolroll