Don't Call A Delegate From Within A Critical Section

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...

::: {style="font-size: 10pt; background: white; color: black; font-family: courier new"} [    1]{style="color: #2b91af"} [//Never call a delegate inside a lock]{style="color: green"}

[    2]{style="color: #2b91af"} [using]{style="color: blue"} System;

[    3]{style="color: #2b91af"} [using]{style="color: blue"} System.Threading;

[    4]{style="color: #2b91af"} [using]{style="color: blue"} System.Collections;

[    5]{style="color: #2b91af"} 

[    6]{style="color: #2b91af"} [delegate]{style="color: blue"} [void]{style="color: blue"} [VoidDelegate]{style="color: teal"}();

[    7]{style="color: #2b91af"} 

[    8]{style="color: #2b91af"} [class]{style="color: blue"} [Library]{style="color: teal"}

[    9]{style="color: #2b91af"} {

[   10]{style="color: #2b91af"}     [VoidDelegate]{style="color: teal"} myDelegate;

[   11]{style="color: #2b91af"}     [public]{style="color: blue"} [VoidDelegate]{style="color: teal"} MyDelegate

[   12]{style="color: #2b91af"}     {

[   13]{style="color: #2b91af"}         [set]{style="color: blue"} { myDelegate = [value]{style="color: blue"}; }

[   14]{style="color: #2b91af"}     }

[   15]{style="color: #2b91af"} 

[   16]{style="color: #2b91af"}     [Thread]{style="color: teal"} t;

[   17]{style="color: #2b91af"} 

[   18]{style="color: #2b91af"}     [public]{style="color: blue"} [void]{style="color: blue"} Run()

[   19]{style="color: #2b91af"}     {

[   20]{style="color: #2b91af"}         [ThreadStart]{style="color: teal"} ts = [new]{style="color: blue"} [ThreadStart]{style="color: teal"}(ThreadCaller);

[   21]{style="color: #2b91af"}         t = [new]{style="color: blue"} [Thread]{style="color: teal"}(ts);

[   22]{style="color: #2b91af"}         t.Name = ["Library thread"]{style="color: maroon"};

[   23]{style="color: #2b91af"}         t.Start();

[   24]{style="color: #2b91af"}     }

[   25]{style="color: #2b91af"} 

[   26]{style="color: #2b91af"}     [public]{style="color: blue"} [void]{style="color: blue"} Stop()

[   27]{style="color: #2b91af"}     {

[   28]{style="color: #2b91af"}         t.Abort();

[   29]{style="color: #2b91af"}         t.Join();

[   30]{style="color: #2b91af"}     }

[   31]{style="color: #2b91af"} 

[   32]{style="color: #2b91af"}     [void]{style="color: blue"} ThreadCaller()

[   33]{style="color: #2b91af"}     {

[   34]{style="color: #2b91af"}         [while]{style="color: blue"} ([true]{style="color: blue"})

[   35]{style="color: #2b91af"}         {

[   36]{style="color: #2b91af"}             [Console]{style="color: teal"}.WriteLine([Thread]{style="color: teal"}.CurrentThread.Name +

[   37]{style="color: #2b91af"}               [" asking for lock on "]{style="color: maroon"} + [this]{style="color: blue"}.GetHashCode());

[   38]{style="color: #2b91af"}             [lock]{style="color: blue"} ([this]{style="color: blue"})

[   39]{style="color: #2b91af"}             {

[   40]{style="color: #2b91af"}                 [Console]{style="color: teal"}.WriteLine([Thread]{style="color: teal"}.CurrentThread.Name +

[   41]{style="color: #2b91af"}                   [" acquired lock"]{style="color: maroon"});

[   42]{style="color: #2b91af"}                 myDelegate();

[   43]{style="color: #2b91af"}                 [Thread]{style="color: teal"}.Sleep(1000);

[   44]{style="color: #2b91af"}             }

[   45]{style="color: #2b91af"}             [Console]{style="color: teal"}.WriteLine([Thread]{style="color: teal"}.CurrentThread.Name +

[   46]{style="color: #2b91af"}               [" released lock"]{style="color: maroon"});

[   47]{style="color: #2b91af"}         }

[   48]{style="color: #2b91af"}     }

[   49]{style="color: #2b91af"} }

[   50]{style="color: #2b91af"} 

[   51]{style="color: #2b91af"} [class]{style="color: blue"} [Client]{style="color: teal"}

[   52]{style="color: #2b91af"} {

[   53]{style="color: #2b91af"}     [internal]{style="color: blue"} [void]{style="color: blue"} FineCall()

[   54]{style="color: #2b91af"}     {

[   55]{style="color: #2b91af"}         LockAndTalk();

[   56]{style="color: #2b91af"}     }

[   57]{style="color: #2b91af"} 

[   58]{style="color: #2b91af"}     [internal]{style="color: blue"} [void]{style="color: blue"} BadCall()

[   59]{style="color: #2b91af"}     {

[   60]{style="color: #2b91af"}         [ThreadStart]{style="color: teal"} ts = [new]{style="color: blue"} [ThreadStart]{style="color: teal"}(LockAndTalk);

[   61]{style="color: #2b91af"}         [Thread]{style="color: teal"} t = [new]{style="color: blue"} [Thread]{style="color: teal"}(ts);

[   62]{style="color: #2b91af"}         t.Name = ["BadClient"]{style="color: maroon"};

[   63]{style="color: #2b91af"}         t.IsBackground = [true]{style="color: blue"};

[   64]{style="color: #2b91af"}         t.Start();

[   65]{style="color: #2b91af"}         [while]{style="color: blue"} (callDone == [false]{style="color: blue"})

[   66]{style="color: #2b91af"}         {

[   67]{style="color: #2b91af"}             [Thread]{style="color: teal"}.Sleep(1000);

[   68]{style="color: #2b91af"}         }

[   69]{style="color: #2b91af"}     }

[   70]{style="color: #2b91af"} 

[   71]{style="color: #2b91af"}     [Library]{style="color: teal"} l;

[   72]{style="color: #2b91af"}     [public]{style="color: blue"} [Library]{style="color: teal"} Library

[   73]{style="color: #2b91af"}     {

[   74]{style="color: #2b91af"}         [set]{style="color: blue"} { l = [value]{style="color: blue"}; }

[   75]{style="color: #2b91af"}     }

[   76]{style="color: #2b91af"} 

[   77]{style="color: #2b91af"}     [protected]{style="color: blue"} [bool]{style="color: blue"} callDone = [false]{style="color: blue"};

[   78]{style="color: #2b91af"} 

[   79]{style="color: #2b91af"}     [public]{style="color: blue"} [void]{style="color: blue"} LockAndTalk()

[   80]{style="color: #2b91af"}     {

[   81]{style="color: #2b91af"}         callDone = [false]{style="color: blue"};

[   82]{style="color: #2b91af"}         [while]{style="color: blue"} (callDone == [false]{style="color: blue"})

[   83]{style="color: #2b91af"}         {

[   84]{style="color: #2b91af"}             [Console]{style="color: teal"}.WriteLine([this]{style="color: blue"}.GetType() +

[   85]{style="color: #2b91af"}               [" asking for lock on "]{style="color: maroon"} + l.GetHashCode());

[   86]{style="color: #2b91af"}             [lock]{style="color: blue"} (l)

[   87]{style="color: #2b91af"}             {

[   88]{style="color: #2b91af"}                 [Console]{style="color: teal"}.WriteLine([Thread]{style="color: teal"}.CurrentThread.Name +

[   89]{style="color: #2b91af"}                   [" acquired lock"]{style="color: maroon"});

[   90]{style="color: #2b91af"}                 [Console]{style="color: teal"}.WriteLine(["Delegate executed"]{style="color: maroon"});

[   91]{style="color: #2b91af"}                 [Thread]{style="color: teal"}.Sleep(1000);

[   92]{style="color: #2b91af"}                 callDone = [true]{style="color: blue"};

[   93]{style="color: #2b91af"}             }

[   94]{style="color: #2b91af"}         }

[   95]{style="color: #2b91af"}         [Console]{style="color: teal"}.WriteLine([Thread]{style="color: teal"}.CurrentThread.Name +

[   96]{style="color: #2b91af"}           [" released lock"]{style="color: maroon"});

[   97]{style="color: #2b91af"}     }

[   98]{style="color: #2b91af"} }

[   99]{style="color: #2b91af"} 

[  100]{style="color: #2b91af"} 

[  101]{style="color: #2b91af"} [class]{style="color: blue"} [TestingClass]{style="color: teal"}

[  102]{style="color: #2b91af"} {

[  103]{style="color: #2b91af"}     [static]{style="color: blue"} [Library]{style="color: teal"} l;

[  104]{style="color: #2b91af"}     TestingClass([Client]{style="color: teal"} c, [VoidDelegate]{style="color: teal"} myDelegate)

[  105]{style="color: #2b91af"}     {

[  106]{style="color: #2b91af"}         l = [new]{style="color: blue"} [Library]{style="color: teal"}();

[  107]{style="color: #2b91af"}         c.Library = l;

[  108]{style="color: #2b91af"}         l.MyDelegate = myDelegate;

[  109]{style="color: #2b91af"}         l.Run();

[  110]{style="color: #2b91af"} 

[  111]{style="color: #2b91af"}         [Thread]{style="color: teal"}.Sleep(10000);

[  112]{style="color: #2b91af"}         [Console]{style="color: teal"}.WriteLine(["Ending test now..."]{style="color: maroon"});

[  113]{style="color: #2b91af"}         l.Stop();

[  114]{style="color: #2b91af"}     }

[  115]{style="color: #2b91af"} 

[  116]{style="color: #2b91af"}     [public]{style="color: blue"} [static]{style="color: blue"} [void]{style="color: blue"} Main()

[  117]{style="color: #2b91af"}     {

[  118]{style="color: #2b91af"}         [Client]{style="color: teal"} c = [new]{style="color: blue"} [Client]{style="color: teal"}();

[  119]{style="color: #2b91af"}         [new]{style="color: blue"} [TestingClass]{style="color: teal"}(c, c.FineCall);

[  120]{style="color: #2b91af"}         [Console]{style="color: teal"}.WriteLine(["Okay, that went fine."]{style="color: maroon"});

[  121]{style="color: #2b91af"} 

[  122]{style="color: #2b91af"}         [Client]{style="color: teal"} c2 = [new]{style="color: blue"} [Client]{style="color: teal"}();

[  123]{style="color: #2b91af"}         [new]{style="color: blue"} [TestingClass]{style="color: teal"}(c2, c2.BadCall);

[  124]{style="color: #2b91af"} 

[  125]{style="color: #2b91af"}     }

[  126]{style="color: #2b91af"} } :::