每秒触发一次事件

时间:2010-03-02 15:58:03

标签: c# .net wpf multithreading

我的应用程序需要每秒检查一次网站提要。

有时对服务器的请求超过一秒钟。在这种情况下,应用程序需要等到第一个请求完成,然后立即启动新请求。我怎么能实现这个呢?

此外,请求不应冻结GUI。

7 个答案:

答案 0 :(得分:8)

我会使用生产者 - 消费者模式的简单变体。你有两个共享整数变量的theads,一个生产者和一个消费者。生产者将有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 Library之外)是Jon Skeet的文档,其中包含“Monitor方法”下的example of producer-consumer

顺便说一下,真正的生产者 - 消费者模式围绕着一组工作数据,一个或多个线程通过向该集合添加数据来产生工作,而一个或多个其他线程消费者通过从该集合中删除数据来工作。在上面的变体中,“工作数据”仅仅是我们需要立即检查Feed的次数。

(另一种方法,而不是让定时器回调调用Consume,用于定时器回调锁定和脉冲消耗等待的Monitor。在这种情况下,Consume有一个无限循环,如while(true),你在自己的主题中开始一次。所以没有必要支持对Monitor.TryEnter的调用重新入侵。)

答案 1 :(得分:4)

我倾向于使用这样的单独线程:

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转换到实际UI更新的UI线程。这非常有效地实现了生产者 - 消费者。

thread.IsBackground = true导致线程在应用程序结束时停止。如果您希望它比此更早停止,请将“停止”值设置为“true”。上面代码中的“Stop”值被假定为类的bool属性,但它可以是任何东西 - 甚至是局部变量。

答案 2 :(得分:1)

Timer对象可能会为您解决问题。您可以将其设置为每秒触发一次。在定时器触发时执行的代码将首先禁用定时器,执行它的工作,并重新启用定时器。通过禁用计时器,它将不会再次触发,直到它完成处理。

答案 3 :(得分:1)

使用这样的计时器:

    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线程上处理的,因此如果您需要从已用过的处理程序更新UI,则必须记住这一点。

答案 4 :(得分:0)

您可以尝试创建一个包含您不想冻结的GUI的主应用程序。它将启动一个带有循环的线程,该循环将在特定时间后迭代。如果计时器等于/超过设定时间,则为请求启动另一个线程。但是,在开始之前检查线程是否已经存在。如果没有,则阻止执行直到请求线程完成。 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
}

注意: 不要每一秒都这样做。正如其他海报所说,这对网站所有者来说有点不尊重。您可能会被网站所有者拒绝访问Feed。

答案 5 :(得分:0)

我认为现在处理此问题的最简单方法是使用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,并在UI线程上运行Subscribe。超级简单!

答案 6 :(得分:0)

使用这样的计时器:

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