Задача службы Windows — отмена опроса

#c# #.net #windows-services #polling #cancellation-token

#c# #.net #windows-services #опрос #отмена-токен

Вопрос:

Я пишу службу Windows и нашел пример, в котором предлагается написать службы опроса Windows следующим образом:

 private void Poll()
{
    CancellationToken cancellationPoll = ctsPoll.Token;
    while (!cancellationPoll.WaitHandle.WaitOne(tsInterval))
    {
        PollDatabase();
        // Occasionally check the cancellation state.
        if (cancellationPoll.IsCancellationRequested)
        {
            break;
        }
    }
}
  

Я немного сбит с толку, когда дело доходит до отмены, и нужны ли мне оба cancellationPoll.Ожидание обработки.WaitOne() и cancellationPoll.Запрашивается ли Cancellation или они делают одно и то же, и требуется только одно?

Комментарии:

1. Вы хотите приостановить выполнение между опросами?

Ответ №1:

!cancellationPoll.WaitHandle.WaitOne(tsInterval) Существует для обеспечения интервала опроса, чтобы у вас было по крайней мере tsIntetval между опросами ( продолжительность операции):

 --tsInterval--|--operation--|--tsInterval--|...
  

Если вы посмотрите на документацию для CancellationToken.WaitHandle , в ней говорится следующее:

Ожидание, которое сигнализируется при отмене токена.

Итак, в вашем случае операции cancellationPoll.IsCancellationRequested достаточно, потому что после нее у вас ничего нет. Но представьте себе ситуацию, подобную этой:

 while (!cancellationPoll.WaitHandle.WaitOne(tsInterval))
{
    //long operation A

    if (cancellationPoll.IsCancellationRequested)
    {
        break;
    }

    //long operation B

    if (cancellationPoll.IsCancellationRequested)
    {
        break;
    }
    //long operation C
}
  

В этом случае имеет смысл время от времени проверять состояние отмены, чтобы избежать выполнения длительной операции…

Комментарии:

1. Я по-прежнему не вижу особого смысла, потому что вы можете указать третий ` IsCancellationRequested` для согласованности обработки и избежать путаницы, подобной возникшей в этой теме.

2. Дело в том, что вы не хотите даже запускать действие B или действие C, если токен был сигнализирован, поэтому вы не хотите ждать следующей итерации цикла… Да, вы можете добавить и третий, но выигрыш незначителен…

3. Я рассматриваю рассуждения только с точки зрения IsCancellationRequested vs WaitHandle и не вижу никаких причин смешивать подходы, поскольку смешивание имеет очевидные недостатки, и тот же результат может быть достигнут только при использовании 3 из IsCancellationRequested .

4. @DmytroMukalov Каковы очевидные недостатки?

5. Наиболее очевидным является удобочитаемость — вы видите, что это вносит путаницу, хотя должно быть легко читаемым. Если ваш читатель кода знаком с одним подходом и незнаком с другим, это заставит его / ее углубиться в детали каждого метода. Для меня это достаточное оправдание, чтобы избежать этого, но, кроме того, существуют аспекты, связанные с перераспределением ресурсов — если CancellationToken происходит из внешних компонентов, которые вы не контролируете, есть вероятность, что CancellationTokenSource жизненный цикл может быть реализован неправильно, и, получая доступ, WaitHandle вы неявно выделяете новый ресурс внутри CTS.

Ответ №2:

Ожидание WaitHanlde здесь избыточно, поскольку с точки зрения результата оно выполняет то же самое, что и IsCancellationRequested — указывает, что запрашивается отмена (но делает это немного по-другому). Итак, для вашего случая вы можете выбрать один метод: либо WaitHandle , либо IsCancellationRequested . Но, пожалуйста, имейте в виду, что это WaitHandle является IDisposable и требует удаления связанного CancellationTokenSource . Если вы решите использовать IsCancellationRequested , не забудьте добавить вызов, который должен перепланировать поток, например, Thread.Sleep чтобы не перегружать ресурсы процессора. Один из сценариев, когда WaitHanlde может быть применен, — это когда вам нужно дождаться дескриптора и вы хотели бы ввести семантику отмены в это ожидание:

  WaitHandle.WaitAny(new [] { handleToWait, cancellationHandle });
  

Комментарии:

1. Неверно, что оба подхода делают одно и то же. Решение для обработки ожидания решает проблему, которую необходимо отменить, пока поток ожидает следующей итерации опроса. IsCancellationRequested Решение решает проблему отмены во время выполнения итерации опроса.

2. @Ackdari Если вы внимательно прочитаете, вы увидите, что я указал, что они делают то же самое с точки зрения результата — вы можете проверить это в исходном коде. То, как они это делают, отличается — это именно то, что я сказал — так что не так с моим утверждением?

3. @DmytroMualov неверно утверждать, что оба решения приводят к одинаковому поведению. Если вы замените WaitOne() на true и добавите вызов на Thread.Sleep() и допустим, время ожидания составит 5 минут. Это приведет к ситуации, когда операция была отменена, но она не будет знать об этом в течение 5 минут.

4. @Ackdari К сожалению, вы не можете внимательно прочитать, потому что поведение и результат — это не одно и то же. Почему вы вообще упоминаете Thread.Sleep ? Это не имеет никакого отношения к токену отмены. Я добавил это как пример вызова для удаления потока из очереди планирования, но не для функции ожидания токена отмены. Если есть другие вызовы перепланирования потока (операции ввода-вывода, ожидания), вам это не нужно. Функциональность ожидания отмены в течение значительного периода времени вообще избыточна, если вы не ждете другого дескриптора, поэтому пример с 5 минутами не имеет значения.

Ответ №3:

!cancellationPoll.WaitHandle.WaitOne(tsInterval) Необходимо, чтобы вы не ждали все время. WaitOne(tsInterval) либо вернется, потому что токен получил сингнал для отмены, либо потому, что время истекло. Если токен получил сигнал на отмену, он WaitOne(tsInterval) вернет true и, таким образом, завершит цикл.

Например, если бы вы сделали что-то вроде:

 while(true)
{
    // long operation
    if (cancellationPoll.IsCancellationRequested)
    {
        break;
    }

    Thread.Sleep(tsInterval);
}
  

если затем отмена запрашивается повторно, в то время как поток заблокирован Thread.Sleep() , вся операция не будет знать, что отмена запрашивается повторно, пока Thread.Sleep() не будет завершена и следующий запуск цикла не перейдет к if инструкции.

Комментарии:

1. Thread.Sleep Это предположение, неизвестно, нужно ли это или нет. Учитывая, что PollDatabase это подразумевает некоторый ввод-вывод, это вообще не нужно. Но даже если вам нужна функциональность для приостановки планирования потока, в общем случае обычно используется очень короткий период, который измеряется десятками миллисекунд или меньше, и, учитывая, что сигнал отмены поступает один раз, в среднем сценарии вы получите половину потери этого периода только один раз, что в подавляющем большинстве известных сценариев отмены просто можно пренебречь.

2. @DmytroMukalov Учитывая, что в исходном коде tsInterval дано WaitOne() , я не ошибаюсь, предполагая, что должно быть приостановлено выполнение.