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

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

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

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

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

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

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

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

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

7 ответов


не обязательно есть" лучший способ " написать долгосрочный код обработки событий. Это зависит от того, какое приложение вы разрабатываете.

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

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

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

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

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


Если вы посмотрите на Реактивные Расширения, он обеспечивает элегантный способ сделать это с помощью наблюдаемые шаблон.

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());

кстати, Thread.Sleep - вероятно, не хорошая идея.

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

почему это важно (некоторые подробные explanatiopn образом)?

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

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

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


первый пример, который вы показываете, является довольно неэлегантным способом реализации периодического таймера. .NET имеет ряд объектов timer, которые делают такие вещи почти тривиальными. Посмотрите в 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 миллисекунд (или около того).


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


используйте 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


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

(new ManualResetEvent(false)).WaitOne(500); //Waits 500ms