Запуск события каждую секунду - PullRequest
9 голосов
/ 02 марта 2010

У меня есть приложение, которое должно проверять фид веб-сайтов каждую секунду.

Иногда запрос к серверу дольше секунды. В этом случае приложение должно дождаться завершения первого запроса, а затем немедленно начать новый запрос. Как я мог это реализовать?

Кроме того, запрос не должен заморозить графический интерфейс.

Ответы [ 7 ]

8 голосов
/ 02 марта 2010

Я бы использовал простую вариацию модели производитель-потребитель. У вас будет две команды, производитель и потребитель, которые имеют целочисленную переменную. У производителя будет System.Threading.Timer, который срабатывает каждую секунду, и тогда он будет Interlocked.Increment переменной и вызовет потребителя. Логика потребителя повторно проверяет подачу и Interlocked.Decrement счетчик, в то время как счетчик больше нуля. Потребительская логика будет защищена Monitor.TryEnter, который будет обрабатывать повторный вход. Вот пример кода.

public static FeedCheck{
  int _count = 0;
  static object _consumingSync = new object();
  static Threading.Timer _produceTimer;

  private static void Consume() {
    if (!Monitor.TryEnter(_consumingSync)) return;

    try {
      while(_count > 0) {
        // check feed
        Interlocked.Decrement(ref _count);
      }
    }
    finally { Monitor.Exit(_consumingSync); }
  }

  private static void Produce() {
    Interlocked.Increment(ref _count);
    Consume();
  }

  public static void Start() {
    // small risk of race condition here, but not necessarily
    // be bad if multiple Timers existed for a moment, since only
    // the last one will survive.
    if (_produceTimer == null) {
      _produceTimer = new Threading.Timer(
        _ => FeedCheck.Produce(), null, 0, 1000
      );
    }
  }
}

Использование:

FeedCheck.Start();

Хорошим ресурсом по .NET Threading (помимо материалов библиотеки MSDN) является документация Джона Скита, которая включает в себя этот пример производителя-потребителя в разделе "Дополнительные Monitor методы".

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

(Другой способ сделать это, вместо вызова обратного вызова таймера, состоит в том, чтобы обратный вызов таймера блокировал и передавал Monitor, который ожидает Consume. В этом случае Consume имеет бесконечный цикл, такой как while(true), который вы запускаете один раз в своем собственном потоке. Поэтому нет необходимости поддерживать повторный вход с помощью вызова Monitor.TryEnter.)

4 голосов
/ 02 марта 2010

Я был бы склонен использовать отдельный поток, как это:

var thread = new Thread(() =>
{
  while(!Stop)
  {
    var nextCheck = DateTime.Now.AddSeconds(1);

    CheckWebSite();
    Application.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() =>
    {
      UpdateViewModelWithNewData();
    }));
    int millis = (int)(nextCheck - DateTime.Now).TotalMilliseconds();
    if(millis>0)
      Thread.Sleep(millis);
  }
});
thread.IsBackground = true;
thread.Start();

Dispatcher.Invoke переходит в поток пользовательского интерфейса для фактического обновления пользовательского интерфейса. Это очень эффективно реализует производитель-потребитель.

thread.IsBackground = true вызывает остановку потока, когда ваше приложение заканчивается. Если вы хотите, чтобы он остановился раньше, установите значение «Стоп» на «истина». Предполагается, что значение «Stop» в приведенном выше коде является свойством bool класса, но это может быть что угодно - даже локальная переменная.

1 голос
/ 03 марта 2010

Используйте таймер, подобный следующему:

    System.Timers.Timer timer = new System.Timers.Timer(1000);

    public void StartTimer()
    {
        timer.Elapsed += new System.Timers.ElapsedEventHandler(this.TimerHandler);
        timer.Start();
    }

    private void TimerHandler(object sender, System.Timers.ElapsedEventArgs e)
    {
        DateTime start;
        TimeSpan elapsed = TimeSpan.MaxValue;

        timer.Stop();

        while (elapsed.TotalSeconds > 1.0)
        {
            start = DateTime.Now;

            // check your website here

            elapsed = DateTime.Now - start;
        }
        timer.Interval = 1000 - elapsed.TotalMilliseconds;
        timer.Start();
    }

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

1 голос
/ 02 марта 2010

Объект Timer может помочь вам. Вы можете включить его каждую секунду. Код, который выполняется при срабатывании таймера, сначала отключит таймер, сделает его работу и снова включит таймер. При отключении таймера он не сработает снова, пока не завершит обработку.

0 голосов
/ 26 июля 2014

используйте таймер как это:

System.Timers.Timer timer = new System.Timers.Timer(1000);

public void StartTimer()
{
    timer.Elapsed += new System.Timers.ElapsedEventHandler(this.TimerHandler);
    timer.Start();
}

private void TimerHandler(object sender, System.Timers.ElapsedEventArgs e)
{
    DateTime start;
    TimeSpan elapsed = TimeSpan.MaxValue;

    timer.Stop();

    while (elapsed.TotalSeconds > 1.0)
    {
        start = DateTime.Now;

        // check your website here

        elapsed = DateTime.Now - start;
    }
    timer.Interval = 1000 - elapsed.TotalMilliseconds;
    timer.Start();
}
0 голосов
/ 16 марта 2014

Я думаю, что самый простой способ справиться с этим сейчас - это использовать Microsoft Reactive Framework (Rx).

Итак, при условии, что у меня есть функция, которая будет вызывать фид сайта и возвращать объект Response, например:

Func<Response> checkWebsiteFeed = ...;

Тогда я могу просто сделать это, чтобы подключить все это:

var query =
    Observable
        .Interval(TimeSpan.FromSeconds(1.0))
        .Select(x => checkWebsiteFeed())            ;

query
    .ObserveOnDispatcher()
    .Subscribe(x =>
    {
        /* do something with response on UI thread */
    });

checkWebsiteFeed вызывается в фоновом потоке, а Subscribe запускается в потоке пользовательского интерфейса. Супер просто!

0 голосов
/ 02 марта 2010

вы можете попробовать создать главное приложение, которое будет содержать графический интерфейс, который вы не захотите замораживать. он запускает поток с циклом, который будет повторяться через определенное время. если таймер равен / превышает установленное время, запустите другой поток для запроса. Но проверьте, если поток уже существует, прежде чем начать. Если нет, то блокируйте выполнение, пока поток запроса не будет завершен. e.g.:

Main
{
    Create gui;
    Start loop thread;
}

Loop thread
{
    loop while not exit
    {
        timer();
        WaitForResource()
        start RequestThread
    }
}

RequestThread
{
    Lock resource/semaphore
    Request, Update GUI
    free semaphore
}

Примечание: Не делай этого каждую секунду. Как говорили другие авторы, это немного неуважительно по отношению к владельцам сайтов. Вы рискуете получить отказ в доступе к каналу со стороны владельцев сайта.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...