Як підтримувати роботу консолі .NET?


105

Розглянемо додаток Console, який запускає деякі сервіси окремим потоком. Все, що потрібно зробити - це почекати, коли користувач натисне 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
}

Відповіді:


64

ви завжди хочете запобігти використанню циклів, особливо коли ви змушуєте код повторно перевіряти змінні. Це витрачає ресурси процесора і сповільнює вашу програму.

Я б точно сказав перший.


2
+1. Крім того, оскільки boolне оголошено як volatile, існує певна можливість, що наступні читання _quitFlagв whileциклі будуть оптимізовані, що веде до нескінченного циклу.
Адам Робінсон

2
Відсутній рекомендований спосіб зробити це. Я очікував цього як відповідь.
Iúri dos Anjos

30

Крім того, більш простим рішенням є просто:

Console.ReadLine();

Я збирався запропонувати це, але це не зупиниться лише на Ctrl-C
Thomas Levesque

У мене склалося враження, що CTRL-C - лише приклад - будь-який користувальницький вклад для його закриття
Cocowalla

Пам'ятайте, що "Console.ReadLine ()" блокує потоки. Таким чином, програма все ще працює, але нічого не вимагає, як чекати, коли користувач введе рядок
fabriciorissetto

2
@fabriciorissetto У запитанні про OP йдеться про "початок асинхронних речей", тому програма буде виконувати роботу над іншою темою
Cocowalla

1
@Cocowalla я пропустив це. Моє ліжко!
fabriciorissetto

12

Ви можете це зробити (і видалити 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
У моєму випадку я не контролюю нитки, на яких працює асинхронний матеріал.
Орбіт

1) Мені не подобається, що ви перевіряєте клавіші Ctrl + C, а не сигнал, який спрацьовує Ctrl + C. 2) Ваш підхід не працює, якщо програма використовує Завдання замість однієї робочої нитки.
CodesInChaos

2

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

token.WaitHandle.WaitOne();

WaitHandle подається сигнал, коли маркер скасовано.

Я бачив цю техніку, яку використовує Microsoft.Azure.WebJobs.JobHost, де маркер надходить із джерела маркера для скасування WebJobsShutdownWatcher (файл-спостерігач, який закінчує роботу).

Це дає певний контроль над тим, коли програма може закінчитися.


1
Це відмінна відповідь для будь-якого додатка консолі реального світу, який потрібно слухати, CTL+Cоскільки він виконує тривалу операцію або демон, який також повинен витончено закрити свої робочі нитки. Ви зробите це за допомогою CancelToken, і таким чином ця відповідь скористається тим, WaitHandleщо вже існувало б, а не створювало нове.
mdisibio

1

З двох перших краще

_quitEvent.WaitOne();

тому що у другому потік прокидається кожні мілісекунди, він перетвориться на переривання ОС, що дорого


Це хороша альтернатива Consoleметодам, якщо у вас немає приєднаної консолі (тому що, наприклад, програма запускається сервісом)
Marco Sulla

0

Ви повинні робити це так само, як і при програмуванні служби Windows. Ви ніколи не використовуєте оператор while, а замість цього використовуєте делегат. WaitOne () зазвичай використовується під час очікування розпорядження потоками - Thread.Sleep () - недоцільно - Чи думали ви використовувати System.Timers.Timer, використовуючи цю подію, щоб перевірити наявність події вимкнення?

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.