Note that there are some explanatory texts on larger screens.

plurals
  1. POLock statement vs Monitor.Enter method
    primarykey
    data
    text
    <p>I suppose that this is an interesting code example. </p> <p>We have a class -- let's call it <strong>Test</strong> -- with a <strong>Finalize</strong> method. In the <strong>Main</strong> method there are two code blocks where I am using a lock statement and a <strong>Monitor.Enter()</strong> call. Also, I have two instances of the <strong>Test</strong> class here. The experiment is pretty simple: Null the <strong>Test</strong> variable within locking block and then try to collect it manually with the <strong>GC.Collect</strong> method call. So, to see the <strong>Finalize</strong> call I am calling the <strong>GC.WaitForPendingFinalizers</strong> method. Everything is very simple, as you can see.</p> <p>By the definition of the <strong>lock</strong> statement, it's opened by the compiler to the <strong>try</strong>{...}<strong>finally</strong>{..} block, with a <strong>Monitor.Enter</strong> call inside of the <strong>try</strong> block and <strong>Monitor</strong>. Then it exits in the <strong>finally</strong> block. I've tried to implement the <strong>try-finally</strong> block manually.</p> <p>I've expected the same behaviour in both cases -- that of using lock and that of using <strong>Monitor.Enter</strong>. But, surprise, surprise it is different, as you can see below:</p> <pre><code>public class Test { private string name; public Test(string name) { this.name = name; } ~Test() { Console.WriteLine(string.Format("Finalizing class name {0}.", name)); } } class Program { static void Main(string[] args) { var test1 = new Test("Test1"); var test2 = new Test("Tesst2"); lock (test1) { test1 = null; Console.WriteLine("Manual collect 1."); GC.Collect(); GC.WaitForPendingFinalizers(); Console.WriteLine("Manual collect 2."); GC.Collect(); } var lockTaken = false; System.Threading.Monitor.Enter(test2, ref lockTaken); try { test2 = null; Console.WriteLine("Manual collect 3."); GC.Collect(); GC.WaitForPendingFinalizers(); Console.WriteLine("Manual collect 4."); GC.Collect(); } finally { System.Threading.Monitor.Exit(test2); } Console.ReadLine(); } } </code></pre> <p>The output of this example is:</p> <blockquote> <p>Manual collect 1. Manual collect 2. Manual collect 3. Finalizing class name Test2. Manual collect 4. And null reference exception in last finally block because test2 is null reference.</p> </blockquote> <p>I was surprised and disassembled my code into IL. So, here is the IL dump of <strong>Main</strong> method:</p> <pre><code>.entrypoint .maxstack 2 .locals init ( [0] class ConsoleApplication2.Test test1, [1] class ConsoleApplication2.Test test2, [2] bool lockTaken, [3] bool &lt;&gt;s__LockTaken0, [4] class ConsoleApplication2.Test CS$2$0000, [5] bool CS$4$0001) L_0000: nop L_0001: ldstr "Test1" L_0006: newobj instance void ConsoleApplication2.Test::.ctor(string) L_000b: stloc.0 L_000c: ldstr "Tesst2" L_0011: newobj instance void ConsoleApplication2.Test::.ctor(string) L_0016: stloc.1 L_0017: ldc.i4.0 L_0018: stloc.3 L_0019: ldloc.0 L_001a: dup L_001b: stloc.s CS$2$0000 L_001d: ldloca.s &lt;&gt;s__LockTaken0 L_001f: call void [mscorlib]System.Threading.Monitor::Enter(object, bool&amp;) L_0024: nop L_0025: nop L_0026: ldnull L_0027: stloc.0 L_0028: ldstr "Manual collect." L_002d: call void [mscorlib]System.Console::WriteLine(string) L_0032: nop L_0033: call void [mscorlib]System.GC::Collect() L_0038: nop L_0039: call void [mscorlib]System.GC::WaitForPendingFinalizers() L_003e: nop L_003f: ldstr "Manual collect." L_0044: call void [mscorlib]System.Console::WriteLine(string) L_0049: nop L_004a: call void [mscorlib]System.GC::Collect() L_004f: nop L_0050: nop L_0051: leave.s L_0066 L_0053: ldloc.3 L_0054: ldc.i4.0 L_0055: ceq L_0057: stloc.s CS$4$0001 L_0059: ldloc.s CS$4$0001 L_005b: brtrue.s L_0065 L_005d: ldloc.s CS$2$0000 L_005f: call void [mscorlib]System.Threading.Monitor::Exit(object) L_0064: nop L_0065: endfinally L_0066: nop L_0067: ldc.i4.0 L_0068: stloc.2 L_0069: ldloc.1 L_006a: ldloca.s lockTaken L_006c: call void [mscorlib]System.Threading.Monitor::Enter(object, bool&amp;) L_0071: nop L_0072: nop L_0073: ldnull L_0074: stloc.1 L_0075: ldstr "Manual collect." L_007a: call void [mscorlib]System.Console::WriteLine(string) L_007f: nop L_0080: call void [mscorlib]System.GC::Collect() L_0085: nop L_0086: call void [mscorlib]System.GC::WaitForPendingFinalizers() L_008b: nop L_008c: ldstr "Manual collect." L_0091: call void [mscorlib]System.Console::WriteLine(string) L_0096: nop L_0097: call void [mscorlib]System.GC::Collect() L_009c: nop L_009d: nop L_009e: leave.s L_00aa L_00a0: nop L_00a1: ldloc.1 L_00a2: call void [mscorlib]System.Threading.Monitor::Exit(object) L_00a7: nop L_00a8: nop L_00a9: endfinally L_00aa: nop L_00ab: call string [mscorlib]System.Console::ReadLine() L_00b0: pop L_00b1: ret .try L_0019 to L_0053 finally handler L_0053 to L_0066 .try L_0072 to L_00a0 finally handler L_00a0 to L_00aa </code></pre> <p>I don't see any difference between the <strong>lock</strong> statement and the <strong>Monitor.Enter</strong> call. So, why do I still have a reference to the instance of <strong>test1</strong> in the case of <strong>lock</strong>, and the object is not collected by GC, but in the case of using <strong>Monitor.Enter</strong> it is collected and finalized?</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload