【问题标题】:Using a semaphore instead of while loop. Is this good or bad?使用信号量而不是 while 循环。这是好事还是坏事?
【发布时间】:2011-12-01 19:18:49
【问题描述】:

我有一个进程在它自己的线程中运行,并且可以在没有阻塞的情况下启动/停止。这最终将进入 Windows 服务,但我现在在控制台应用程序中设置它,直到它完全充实。

在调用 Start() 之后,我希望主程序线程阻塞,直到按下 Ctrl-C。我知道这会奏效:

public static void Main(string[] args)
{
    bool keepGoing = true;

    var service = new Service();

    System.Console.TreatControlCAsInput = false;

    System.Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e)
    {
        e.Cancel = true;
        service.Stop();
        keepGoing = false; // Break the while loop below
    };

    service.Start();

    while( keepGoing )
    {
        Thread.Sleep(100); // 100 is arbitrary
    }

}

但是,我发现标志和任意睡眠值很麻烦。我知道在 while 循环中 CPU 成本实际上是 0,但我宁愿有一个“硬”块,在 Ctrl-C 处理程序完成后立即释放。我设计了以下内容,使用信号量阻止匿名 Ctrl-C 处理程序完成:

public static void Main(string[] args)
{
    var service = new Service();

    var s = new Semaphore(1, 1);

    System.Console.TreatControlCAsInput = false;

    System.Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e)
    {
        e.Cancel = true;
        service.Stop();
        s.Release(); // This will allow the program to conclude below
    };

    service.Start();

    s.WaitOne(); // This will not block
    s.WaitOne(); // This will block w/o CPU usage until the sempahore is released

}

这是一个糟糕的设计吗?是不是矫枉过正?危险吗?

编辑:

我还连接了 AppDomain.CurrentDomain.UnhandledException 如下:

AppDomain.CurrentDomain.UnhandledException += delegate {
  service.Stop();
  s.Release();
};

编辑第二个:

我应该注意到,在退出时调用 Stop() 方法至关重要。 @Adam Ralph 有一个非常好的混合控制台/服务模式,但在回答问题时没有此信息。

【问题讨论】:

  • 如果你能避免while循环,我说它值得追求。
  • 对于原型设计,后者是一个很大的改进。在生产应用程序中,我会避​​免整个“CTRL + C”中断想法。改为使用信号终止线程。信号Set() 的方式取决于所涉及的其他层。在设计服务时请记住这一点。添加一个方法,比如可以调用的方法,依次调用Set()
  • @P.Brian.Mackey:虽然这不是一个生产应用程序,但如果它,那么至少在非交互式控制台应用程序?实际上,Ctrl-C 将简单地关闭程序,而没有机会在退出前进行清理。最终,我只是想知道“while (flag) Thread.Sleep(...)”选项的信号量解决方案是否。
  • 你可以用一个事件来做同样的事情。如果您在关闭之前等待多个线程,则信号量会更合适,这对于这种情况来说是多余的。一般来说,这是一个很好的做法 - 避免轮询。
  • 我建议使用ManualResetEvent 而不是Semaphore。如果您必须跨进程通信,或者命名为EventWaitHandle

标签: c# multithreading semaphore


【解决方案1】:

我们在一些应用中有类似的要求。它们是 Windows 服务,但为了调试,我们通常希望将它们作为控制台应用程序运行。此外,我们通常会在较早的时候将新应用程序编码为 Windows 服务,但通常不希望等到后来,一旦我们证明了这个概念等,就必须将它们作为服务实际运行。

这是我们使用的模式:-

using (var service = new Service())
{
    if (Environment.UserInterActive)
    {
        service.Start();
        Thread.Sleep(Timeout.Infinite);
    }
    else
    {
        ServiceBase.Run(service);
    }
}

让线程无限休眠可能看起来效率低下,但这只是用于调试场景,冗余线程不消耗 CPU 时间,只是一些内存(约 1MB),主要由分配给线程的堆栈空间组成。该过程仍然可以通过 Ctrl+C 或关闭命令窗口来退出。

-- 编辑--

如果您发现在按下 Ctrl+C 时没有调用 service.Dispose()(即发生粗鲁的中止)并且对 Dispose() 的调用至关重要,那么我想您可以像这样明确地这样做:-

using (var service = new Service())
{
    if (Environment.UserInterActive)
    {
        Console.CancelKeyPress += (sender, e) => service.Dispose();
        service.Start();
        Thread.Sleep(Timeout.Infinite);
    }
    else
    {
        ServiceBase.Run(service);
    }
}

注意Stop()应该封装在Dispose()中。

【讨论】:

  • 这是否仍然存在以下问题:在 UserInterActive 上下文中,如果用户尝试停止进程(使用 Ctrl-C 或窗口关闭),Stop()方法不会被调用?这就是我在上面的示例中要完成的任务。
  • 在我们的服务中,调用 Stop 方法并不重要,但我想您仍然可以订阅 CancelKeyPress 并明确执行此操作。我会编辑答案
  • 我在 Q 中并不清楚这一点;对此感到抱歉。我编辑提到了这一点。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-01-14
  • 2020-10-21
  • 2011-10-06
  • 2011-04-02
  • 2013-01-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多