Lock 语句与 Monitor.Enter 方法 [英] Lock statement vs Monitor.Enter method

查看:25
本文介绍了Lock 语句与 Monitor.Enter 方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想这是一个有趣的代码示例.

I suppose that this is an interesting code example.

我们有一个类——我们称之为Test——带有一个Finalize方法.在 Main 方法中有两个代码块,我在其中使用了一个 lock 语句和一个 Monitor.Enter() 调用.另外,我在这里有两个 Test 类的实例.实验非常简单:将锁定块中的 Test 变量清空,然后尝试使用 GC.Collect 方法调用手动收集它.因此,要查看 Finalize 调用,我正在调用 GC.WaitForPendingFinalizers 方法.如您所见,一切都非常简单.

We have a class -- let's call it Test -- with a Finalize method. In the Main method there are two code blocks where I am using a lock statement and a Monitor.Enter() call. Also, I have two instances of the Test class here. The experiment is pretty simple: Null the Test variable within locking block and then try to collect it manually with the GC.Collect method call. So, to see the Finalize call I am calling the GC.WaitForPendingFinalizers method. Everything is very simple, as you can see.

根据lock语句的定义,它被编译器打开到try{...}finally{..}块,在 try 块和 Monitor 内调用 Monitor.Enter.然后它在 finally 块中退出.我尝试手动实现 try-finally 块.

By the definition of the lock statement, it's opened by the compiler to the try{...}finally{..} block, with a Monitor.Enter call inside of the try block and Monitor. Then it exits in the finally block. I've tried to implement the try-finally block manually.

我预计两种情况下的行为相同——使用锁和使用 Monitor.Enter.但是,令人惊讶的是,它是不同的,如下所示:

I've expected the same behaviour in both cases -- that of using lock and that of using Monitor.Enter. But, surprise, surprise it is different, as you can see below:

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();
    }
}

这个例子的输出是:

手动收集1.手动收集2.手动收集 3. 结束类名称 Test2.手动收集 4.最后一个 finally 块中的空引用异常,因为 test2 是空引用.

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.

我很惊讶并将我的代码反汇编成 IL.所以,这里是 Main 方法的 IL 转储:

I was surprised and disassembled my code into IL. So, here is the IL dump of Main method:

.entrypoint
.maxstack 2
.locals init (
    [0] class ConsoleApplication2.Test test1,
    [1] class ConsoleApplication2.Test test2,
    [2] bool lockTaken,
    [3] bool <>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 <>s__LockTaken0
L_001f: call void [mscorlib]System.Threading.Monitor::Enter(object, bool&)
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&)
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

我看不出 lock 语句和 Monitor.Enter 调用之间有什么区别.那么,为什么我在lock的情况下还是有对test1的实例的引用,而且对象不是被GC回收的,而是在使用Monitor.Enter 是否收集完毕?

I don't see any difference between the lock statement and the Monitor.Enter call. So, why do I still have a reference to the instance of test1 in the case of lock, and the object is not collected by GC, but in the case of using Monitor.Enter it is collected and finalized?

推荐答案

是因为test1指向的引用赋值给了局部变量CS$2$0000IL代码.您在 C# 中将 test1 变量设为空,但 lock 构造会以维护单独引用的方式进行编译.

It is because the reference pointed to by test1 is assigned to the local variable CS$2$0000 in the IL code. You null out the test1 variable in C#, but the lock construct gets compiled in such a manner that a separate reference is maintained.

C# 编译器执行此操作实际上非常聪明.否则有可能绕过 lock 语句应该强制在退出临界区时释放锁的保证.

It is actually quite clever that the C# compiler does this. Otherwise it would be possible to circumvent the guarentee the lock statement is supposed to enforce of releasing the lock upon exiting the critical section.

这篇关于Lock 语句与 Monitor.Enter 方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆