Console.ReadKey和Console.ReadLine在计时器方面的区别

13
以下代码是一个常见的例子,用于展示调试版本和发布版本之间的区别:
using System;
using System.Threading;

public static class Program
{
    public static void Main()
    {
        Timer t = new Timer(TimerCallback, null, 0, 2000);
        Console.ReadLine();
    }

    private static void TimerCallback(Object o)
    {
        Console.WriteLine("In TimerCallback: " + DateTime.Now);
        GC.Collect();
    }
}

如果您使用调试配置运行此代码,则计时器将每两秒输出当前时间。由于编译器人为地延长了Timer t变量的生命周期,因此GC.Collect没有任何效果。在发布配置中,计时器将只执行一次。GC.Collect将垃圾回收t变量,这就是全部内容。
这一切都按照应有的方式工作。奇怪的是,当您将Console.ReadLine更改为Console.ReadKey时,两个配置都会每两秒运行一次计时器。
Console.ReadKey和Console.ReadLine之间有什么区别?我从文档中了解到,Console.ReadKey会阻塞发出ReadKey方法的线程。但是GC.Collect仍然会触发.. 为什么通过阻塞主线程来扩展Timer t的生命周期? 更新
在使用.NET 3.5时,不会发生这种情况!
1个回答

10
Console.ReadKey()方法会锁定Console.InternalSyncObject,但是Console.ReadLine()方法不会。当TimerCallBack()方法尝试写入Console时,由于仍然锁定了Console.InternalSyncObject,线程会等待。因此,GC.Collect()从未被调用。只要按下任意键,锁就会释放并调用GC.Collect()
我将您的代码更改为以下方式,它不会锁定Console.InternalSyncObject,并且在Release版本中只会响一次,在Debug版本中每2秒钟响一次。
private static void TimerCallback(Object o)
{
    Console.Beep();
    GC.Collect();
}

Console.WriteLine()等待的原因是在第一次创建Console.Out TextWriter时,它尝试获取锁定Console.InternalSyncObject。将你的代码更改为以下内容即可按预期工作,因为我们在启动计时器之前创建Console.Out TextWriter。
public static void Main()
{
    Console.WriteLine("Loaded");
    Timer t = new Timer(TimerCallback, null, 0, 2000);
    Console.ReadKey();
}

private static void TimerCallback(Object o)
{
    Console.WriteLine("In TimerCallback: " + DateTime.Now);
    GC.Collect();
}

这是由于.NET 4.5的更改导致的。更多信息请点击此处


1
当你在评论时,我刚刚添加了更多信息。我发现控制台在第一次创建Out TextWriter时尝试获取锁定。 - Alex Wiese
有关为什么.NET 3.5会以不同的方式处理事情的任何想法吗?可能与async/await/TPL有关.. - Wouter de Kort
1
我刚刚检查了一下,ReadKey()及其重载在.NET 3.5中不会锁定InternalSyncObject - Alex Wiese
很酷 :) 我知道控制台实现了一些锁定机制,但从未研究过它的实际工作原理。 - Wouter de Kort
@alexw .NET 4.0是.NET 4.5的就地更新。一旦在计算机上安装了.NET 4.0,它将出现竞争条件。 - Wouter de Kort
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接