如何让.NET控制台应用程序保持运行?

133

考虑一个控制台应用程序,它在单独的线程中启动一些服务。 它所需要做的就是等待用户按下Ctrl + C来关闭它。

以下哪种方法更好?

static ManualResetEvent _quitEvent = new ManualResetEvent(false);

static void Main() {
    Console.CancelKeyPress += (sender, eArgs) => {
        _quitEvent.Set();
        eArgs.Cancel = true;
    };

    // kick off asynchronous stuff 

    _quitEvent.WaitOne();

    // cleanup/shutdown and quit
}

或者使用 Thread.Sleep(1):

static bool _quitFlag = false;

static void Main() {
    Console.CancelKeyPress += delegate {
        _quitFlag = true;
    };

    // kick off asynchronous stuff 

    while (!_quitFlag) {
        Thread.Sleep(1);
    }

    // cleanup/shutdown and quit
}
9个回答

81

你总是希望避免使用while循环,特别是当你强制代码重新检查变量时。这会浪费CPU资源并减慢程序速度。

我肯定会选择第一个。


3
此外,由于bool未声明为volatile,因此在while循环中对_quitFlag的后续读取可能会被优化掉,导致无限循环。 - Adam Robinson
2
缺少推荐的方法来完成这个。我本来期望会得到这样的答案。 - Iúri dos Anjos

36

另外,更简单的解决方案是:

Console.ReadLine();

我正准备建议这个,但它不会仅在按下Ctrl-C时停止。 - Thomas Levesque
2
请记住,'Console.ReadLine()'是线程阻塞的。因此,应用程序仍在运行,但除了等待用户输入一行之外,什么也不做。 - fabriciorissetto
3
OP的问题陈述为“启动异步操作”,因此应用程序将在另一个线程上执行工作。 - Cocowalla
1
@Cocowalla 我错过了那个。我的错! - fabriciorissetto
我刚才意识到的是,在主函数末尾加上一个Console.ReadLine,应用程序会立即停止,可能不会执行CancelKeyPress函数中的全部代码。 - ZerOne
显示剩余4条评论

15

你可以这样做(并删除CancelKeyPress事件处理程序):

while(!_quitFlag)
{
    var keyInfo = Console.ReadKey();
    _quitFlag = keyInfo.Key == ConsoleKey.C
             && keyInfo.Modifiers == ConsoleModifiers.Control;
}

我不确定那是否更好,但我不喜欢在循环中调用 Thread.Sleep,我认为在用户输入上进行阻塞更干净。


我不喜欢你检查 Ctrl+C 键,而不是由 Ctrl+C 触发的信号。 - CodesInChaos

9

我更喜欢使用 Application.Run

static void Main(string[] args) {

   //Do your stuff here

   System.Windows.Forms.Application.Run();

   //Cleanup/Before Quit
}

从文档中得知:

在当前线程上开始运行标准应用程序消息循环,而不需要窗体。


15
但这样一来,你就会仅为此而对Windows Forms产生依赖。传统的.NET框架可能不是太大问题,但当前趋势是采用模块化部署,仅包含所需的部分。 - CodesInChaos

9

也可以基于取消令牌来阻塞线程/程序。

token.WaitHandle.WaitOne();

当令牌被取消时,WaitHandle会被激活。

我曾经看到过Microsoft.Azure.WebJobs.JobHost使用这种技术,其中令牌来自WebJobsShutdownWatcher的取消令牌源(一个终止作业的文件监视器)。

这可以在一定程度上控制程序何时结束。


1
这是一个非常优秀的答案,适用于任何需要监听 CTL+C 的真实世界控制台应用程序,因为它正在执行长时间运行的操作,或者是一个守护进程,应该也能够优雅地关闭其工作线程。您可以使用 CancelToken 来实现这一点,因此此答案利用了已经存在的 WaitHandle,而不是创建一个新的。 - mdisibio

4

看起来你正在把它弄得比必要的更难。为什么不在发出停止信号后直接Join线程呢?

class Program
{
    static void Main(string[] args)
    {
        Worker worker = new Worker();
        Thread t = new Thread(worker.DoWork);
        t.IsBackground = true;
        t.Start();

        while (true)
        {
            var keyInfo = Console.ReadKey();
            if (keyInfo.Key == ConsoleKey.C && keyInfo.Modifiers == ConsoleModifiers.Control)
            {
                worker.KeepGoing = false;
                break;
            }
        }
        t.Join();
    }
}

class Worker
{
    public bool KeepGoing { get; set; }

    public Worker()
    {
        KeepGoing = true;
    }

    public void DoWork()
    {
        while (KeepGoing)
        {
            Console.WriteLine("Ding");
            Thread.Sleep(200);
        }
    }
}

2
在我的情况下,我无法控制异步操作运行的线程。 - intoOrbit
  1. 我不喜欢你检查 Ctrl+C 键,而不是由 Ctrl+C 触发的信号。
  2. 如果应用程序使用任务而不是单个工作线程,则您的方法无法正常工作。
- CodesInChaos

1

在这两个中,第一个更好。

_quitEvent.WaitOne();

因为在第二个例子中,线程每一毫秒都会被唤醒,这将转化为昂贵的操作系统中断。

2
如果您没有控制台附加(例如,程序由服务启动),那么这是“Console”方法的一个很好的替代方案。 - Marco Sulla

0

你应该像编写Windows服务一样去做。你永远不会使用while语句,而是应该使用委托。WaitOne()通常用于等待线程释放,而Thread.Sleep()则不建议使用。你考虑过使用System.Timers.Timer来检查关闭事件吗?


-1

您可以作为主机运行

    public class Program
{
    /// <summary>
    /// Application entry point
    /// </summary>
    /// <param name="args">arguments</param>
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

HostBuilder 是什么

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .UseWindowsService()
            .ConfigureServices((_, services) =>
            {
            })
            .ConfigureLogging(config =>
            {
                config.ClearProviders();
            });

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