c# - Lock 语句与 Monitor.Enter 方法

标签 c# command-line-interface garbage-collection

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

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

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

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

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

这个例子的输出是:

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 转储:

.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回收的,而是在使用的情况下strong>Monitor.Enter 是收集定案了吗?

最佳答案

I do not see any difference between lock statement and Monitor.Enter call.

仔细看。第一种情况将引用复制到第二个局部变量以确保它保持事件状态。

请注意 C# 3.0 规范对这个主题的看法:

A lock statement of the form "lock (x) ..." where x is an expression of a reference-type, is precisely equivalent to

System.Threading.Monitor.Enter(x);
try { ... }
finally { System.Threading.Monitor.Exit(x); }

except that x is only evaluated once.

这是最后一点——除了 x 只计算一次——这是行为的关键。为了确保 x 只被计算一次,我们将它计算一次,将结果存储在局部变量中,并在以后重新使用该局部变量。

在 C# 4 中,我们更改了代码生成器,现在是

bool entered = false;
try { 
  System.Threading.Monitor.Enter(x, ref entered);
  ... 
}
finally { if (entered) System.Threading.Monitor.Exit(x); }

但同样,x 仅被求值一次。在您的程序中,您正在评估锁定表达式两次。你的代码真的应该是

    bool lockTaken = false;   
    var temp = test2;
    try {   
        System.Threading.Monitor.Enter(temp, ref lockTaken);   
        test2 = null;   
        Console.WriteLine("Manual collect 3.");   
        GC.Collect();   
        GC.WaitForPendingFinalizers();   
        Console.WriteLine("Manual collect 4.");   
        GC.Collect();   
    }   
    finally {   
       System.Threading.Monitor.Exit(temp);   
    }  

现在清楚为什么会这样工作了吗?

(另请注意,在 C# 4 中,Enter 位于 try 的内部,而不是 C# 3 中的外部。)

关于c# - Lock 语句与 Monitor.Enter 方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2837070/

相关文章:

Angular CLI 在同一文件夹错误中创建文件

java - 获取引用对象的对象列表

C# - 如何使用帮助提供程序显示帮助文件?

c# - 装饰器模式 - 从装饰器访问包装对象中的值

command-line-interface - 如何在 Midnight Commander 中过滤目录

java - 垃圾收集器 (.net/java) 是实时系统的问题吗?

java - Android 中的垃圾收集器

c# - 在中继器中使用中继器是错误的吗?

c# - 带有 EF Core 的 ASP.NET Core - DTO 集合映射

将在不暂停的情况下循环监听用户输入的 Java 应用程序