Лучшая практика для бесконечного / периодического выполнения кода в C # - PullRequest
22 голосов
/ 15 декабря 2010

Часто в своем коде я запускаю угрозы, которые в основном выглядят так:

void WatchForSomething()
{
    while(true)
    {
        if(SomeCondition)
        {
             //Raise Event to handle Condition
             OnSomeCondition();
        }
        Sleep(100);
    }
}

просто для того, чтобы узнать, является ли какое-либо условие истинным или нет (например, если у вас есть плохо закодированная библиотека без событий, просто булевы переменные, и мне нужно их "live-view").

Теперь мне интересно, есть ли лучший способ выполнить такую ​​работу, как функция Windows, чтобы подключить, которая может запускать мои методы все х сек. Или я должен кодировать глобальное событие для моего приложения, увеличивая все х секунд и позволяя ему вызывать мои методы следующим образом:

//Event from Windows or selfmade
TicEvent += new TicEventHandler(WatchForSomething));

, а затем этот метод:

    void WatchForSomething()
    {
        if(SomeCondition)
        {
             //Raise Event to handle Condition
             OnSomeCondition();
        }
    }

Итак, я надеюсь, что это не закрыто из-за «субъективного вопроса» или чего-то еще, я просто хочу знать, какова лучшая практика для такого рода работы.

Ответы [ 7 ]

18 голосов
/ 15 декабря 2010

Не всегда существует «лучший способ» для написания долговременного кода обработки событий. Это зависит от того, какое приложение вы разрабатываете.

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

Второй подход также часто используется - особенно в системах, в которых уже есть API для управления событиями, например WinForms, WPF или Silverlight. Использование объекта таймера или события Idle является типичным способом какие периодические проверки фона могут быть выполнены, если нет инициируемого пользователем события, которое запускает вашу обработку. Преимущество заключается в том, что легко взаимодействовать и обновлять объекты пользовательского интерфейса (так как они напрямую доступны из одного потока), и это уменьшает потребность в блокировках и мьютексах для защищенных данных. Одним из потенциальных недостатков этого подхода является то, что обработка, которая должна выполняться, отнимает много времени и может сделать ваше приложение не реагирующим на ввод данных пользователем.

Если вы не пишете приложения с пользовательским интерфейсом (например, службы), то первая форма используется гораздо чаще.

В качестве отступления ... когда это возможно, лучше использовать объект синхронизации, такой как EventWaitHandle или Семафор , чтобы сигнализировать, когда работа доступна для обработки. Это позволяет избежать использования объектов Thread.Sleep и / или Timer. Это уменьшает среднюю задержку между тем, когда работа доступна для выполнения, и когда запускается код обработки событий, и минимизирует накладные расходы на использование фоновых потоков, поскольку они могут быть более эффективно запланированы средой выполнения и не будут потреблять циклы ЦП. пока не будет работы.

Стоит также упомянуть, что если ваша обработка выполняется в ответ на обмен данными с внешними источниками (MessageQueues, HTTP, TCP и т. Д.), Вы можете использовать такие технологии, как WCF , чтобы обеспечить каркас обработки ваших событий. код. WCF предоставляет базовые классы, которые существенно упрощают реализацию как клиентских, так и серверных систем, которые асинхронно реагируют на активность событий связи.

12 голосов
/ 15 декабря 2010

Если вы посмотрите на Reactive Extensions , он предоставляет элегантный способ сделать это, используя наблюдаемый шаблон .

var timer = Observable.Interval(Timespan.FromMilliseconds(100));
timer.Subscribe(tick => OnSomeCondition());

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

var seconds = from tick in timer where tick % 10 == 0 select tick;
seconds.Subscribe(tick => OnSomeOtherCondition());
10 голосов
/ 15 декабря 2010

Кстати, Thread.Sleep, вероятно, никогда не будет хорошей идеей.

Основная проблема с Thread.Sleep, о которой люди обычно не знают, заключается в том, что внутренняя реализация Thread.Sleep не качает сообщения STA .Лучшая и самая простая альтернатива, если вам нужно подождать определенное время и вы не можете использовать объект синхронизации ядра, это заменить Thread.Sleep на Thread.Join в текущем потоке с желаемым временем ожидания.Thread.Join будет вести себя так же, т. Е. Поток будет ожидать требуемое время, но в то же время объекты STA будут перекачиваться.

Почему это важно (следует подробное объяснение ниже)?

Иногда, даже не зная, что один из ваших потоков создал объект STA COM.(Например, иногда это происходит за кулисами, когда вы используете Shell API).Теперь предположим, что ваш поток создал COM-объект STA и теперь вызывает Thread.Sleep.Если в какой-то момент COM-объект должен быть удален (что может произойти в неожиданное время с помощью GC), то поток Finalizer попытается вызвать distruvtor объекта.Этот вызов будет направлен в поток STA объекта, который будет заблокирован.

Теперь фактически у вас будет заблокированный поток финализатора.В этой ситуации объекты не могут быть освобождены из памяти, и за ними последуют плохие вещи.

Итак, суть: Thread.Sleep = плохо.Thread.Join = разумная альтернатива.

5 голосов
/ 15 декабря 2010

Первый пример, который вы показываете, является довольно не элегантным способом реализации периодического таймера. В .NET есть несколько объектов таймера, которые делают подобные вещи почти тривиальными. Посмотрите на System.Windows.Forms.Timer, System.Timers.Timer и System.Threading.Timer.

Например, вот как вы бы использовали System.Threading.Timer вместо первого примера:

System.Threading.Timer MyTimer = new System.Threading.Timer(CheckCondition, null, 100, 100);

void CheckCondition(object state)
{
    if (SomeCondition())
    {
        OnSomeCondition();
    }
}

Этот код будет вызывать CheckCondition каждые 100 миллисекунд (или около того).

0 голосов
/ 01 сентября 2016

Очень простой способ не блокировать ожидание других потоков / задач:

(new ManualResetEvent(false)).WaitOne(500); //Waits 500ms
0 голосов
/ 15 декабря 2010

Используйте BackgroundWoker для дополнительных мер безопасности потока:

BackgroundWorker bw = new BackgroundWorker();


bw.WorkerSupportsCancellation = true;


bw.WorkerReportsProgress = true;
.
.
.
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker worker = sender as BackgroundWorker;

    for (;;)
    {
        if (worker.CancellationPending == true)
        {
           e.Cancel = true;

           break;
        }

        else
        {
            // Perform a time consuming operation and report progress.

            System.Threading.Thread.Sleep(100);
        }
    }
}

Для получения дополнительной информации посетите: http://msdn.microsoft.com/en-us/library/cc221403%28v=vs.95%29.aspx

0 голосов
/ 15 декабря 2010

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

...