Использование семафора вместо цикла while. Это хорошо или плохо? - PullRequest
6 голосов
/ 01 декабря 2011

У меня есть процесс, который работает в своем собственном потоке и может быть запущен / остановлен без блокировки.Это в конечном итоге перейдет в службу 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 стоимость процессора практически равна 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();
};

РЕДАКТИРОВАТЬ2-й:

Следует отметить, что крайне важно, чтобы метод Stop() вызывался при выходе.У @ Adam Ralph очень хороший образец для гибридной консоли / службы, но он не имел этой информации при ответе на вопрос.

1 Ответ

4 голосов
/ 01 декабря 2011

У нас есть похожее требование в некоторых наших приложениях. Это службы Windows, но для отладки мы часто хотим запускать их как консольные приложения. Более того, мы обычно кодируем новые приложения как сервисы Windows довольно рано, но часто не хотим фактически запускать их как сервис до тех пор, пока мы не проверим концепцию и т. Д.

Это шаблон, который мы используем: -

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

Указание потоку на бесконечный спящий режим может показаться неэффективным, но это только для сценариев отладки, а резервный поток не требует времени ЦП, а только некоторой памяти (около 1 МБ), которая в основном состоит из стекового пространства, выделенного для потока. Процесс по-прежнему можно завершить с помощью Ctrl + C или путем закрытия окна команд.

- РЕДАКТИРОВАТЬ -

Если вы обнаружите, что service.Dispose() не вызывается при нажатии Ctrl + C (т. Е. Происходит грубое прерывание), и вызов 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().

...