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 }