Как поддерживать работу консольного приложения .NET?

105

Рассмотрим консольное приложение, которое запускает некоторые службы в отдельном потоке. Все, что ему нужно сделать, это дождаться, пока пользователь нажмет 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
}
intoOrbit
источник

Ответы:

64

вы всегда хотите предотвратить использование циклов while, особенно когда вы заставляете код повторно проверять переменные. Это тратит ресурсы ЦП и замедляет работу вашей программы.

Однозначно скажу первое.

Майк
источник
2
+1. Кроме того, поскольку boolне объявлен как volatile, существует определенная вероятность того, что последующие чтения _quitFlagв whileцикле будут оптимизированы, что приведет к бесконечному циклу.
Адам Робинсон
2
Отсутствует рекомендуемый способ сделать это. Я ожидал этого в качестве ответа.
Iúri dos Anjos
30

В качестве альтернативы более простое решение:

Console.ReadLine();
Cocowalla
источник
Я собирался предложить это, но это не остановится только на Ctrl-C
Томас Левеск
У меня сложилось впечатление, что CTRL-C был просто примером - любой пользовательский ввод, чтобы закрыть его
Cocowalla
Помните, что Console.ReadLine () блокирует потоки. Таким образом, приложение будет по-прежнему работать, но ничего не будет делать, кроме как ждать, пока пользователь
введет
2
@fabriciorissetto В вопросе OP говорится: «начать асинхронный процесс», поэтому приложение будет выполнять работу в другом потоке
Cocowalla 01
1
@Cocowalla Я пропустил это. Виноват!
fabriciorissetto 01
13

Вы можете сделать это (и удалить 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
}

из документов:

Начинает запускать стандартный цикл сообщений приложения в текущем потоке без формы.

Игарадон
источник
10
Но тогда вы зависите от форм Windows только для этого. Не такая уж большая проблема с традиционной платформой .NET, но текущая тенденция заключается в модульном развертывании, включающем только те части, которые вам нужны.
CodesInChaos
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 07
1) Мне не нравится, что вы проверяете клавиши Ctrl + C вместо сигнала, запускаемого Ctrl + C. 2) Ваш подход не работает, если приложение использует Задачи вместо одного рабочего потока.
CodesInChaos
3

Также возможно заблокировать поток / программу на основе токена отмены.

token.WaitHandle.WaitOne();

WaitHandle получает сигнал, когда токен отменяется.

Я видел этот метод, используемый Microsoft.Azure.WebJobs.JobHost, где токен поступает из источника токена отмены WebJobsShutdownWatcher (наблюдателя за файлами, который завершает задание).

Это дает некоторый контроль над тем, когда программа может закончиться.

CRice
источник
1
Это отличный ответ для любого реального консольного приложения, которому необходимо прослушивать a, CTL+Cпотому что оно выполняет длительную операцию или является демоном, который также должен корректно завершать свои рабочие потоки. Вы бы сделали это с помощью CancelToken, и поэтому этот ответ использует WaitHandleуже существующий, а не создает новый.
mdisibio
1

Из двух первый лучше

_quitEvent.WaitOne();

потому что во втором поток просыпается каждую миллисекунду и превращается в прерывание ОС, что дорого

Naveen
источник
Это хорошая альтернатива для Consoleметодов, если у вас нет подключенной консоли (потому что, например, программа запускается службой)
Марко Сулла
0

Вы должны сделать это так же, как если бы вы программировали службу Windows. Вы бы никогда не использовали оператор while, вместо этого вы использовали бы делегат. WaitOne () обычно используется при ожидании удаления потоков - Thread.Sleep () - не рекомендуется. Думали ли вы об использовании System.Timers.Timer, используя это событие для проверки события завершения работы?

KenL
источник