views:

223

answers:

5

I have an application that needs to check a website feed every second.

Sometimes the request to the server is longer than a second. In this case, the application needs to wait until the first request completes, then start a new request immediately. How could I implement this?

Also, the request should not freeze the GUI.

+1  A: 

A Timer object might do the trick for you. You can set it to fire every second. The code that executes upon the Timer firing would first disable the timer, do it's work, and re-enable the timer. By disabling the timer, it won't fire again, until it's done processing.

Randy Minder
If request to website last more than second, after request complete application will wait for a second before send new request, but I need it to send new request immediately
R.Gregory
+3  A: 

I would be inclined to use a separate thread like this:

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

The Dispatcher.Invoke transitions to the UI thread for the actual UI update. This implements producer-consumer very efficiently.

The thread.IsBackground = true causes the thread to stop when your application ends. If you want it to stop sooner than that, set a "Stop" value to "true". The "Stop" value in the above code is assumed to be a bool property of the class, but it could be anything - even a local variable.

Ray Burns
Yes, i think it will work. I have one more question, if i have more than one site to check (for example 10). And i want to fetch feed from them in parallel. I will need to start new thread for every site?Is it a good idia?
R.Gregory
If there are only 10 sites I would stick with this solution and just start 10 threads. If there are 100 sites, I would use `WebRequest.BeginGetResponse` and `IAsyncResult` instead. But make sure your incoming pipe is big enough so you don't start dropping packets.
Ray Burns
+6  A: 

I would use a simple variation of the producer-consumer pattern. You'd have two theads, a producer and a consumer, that share an integer variable. The producer would have a System.Threading.Timer that fired every second, at which time it would Interlocked.Increment the variable and call the consumer. The consumer logic repeatedly checks the feed and Interlocked.Decrement the counter, while the counter is greater than zero. The consumer logic would be protected by a Monitor.TryEnter which would handle re-entrancy. Here's sample code.

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

  private static void Consume() {
    if (Monitor.TryEnter(_consumingSync)) {
       while(_count > 0) {
         // check feed
         Interlocked.Decrement(ref _count);
       }
    }
  }

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

Usage:

FeedCheck.Start();

A good resource on .NET Threading (besides the MSDN Library stuff) is Jon Skeet's documentation, which includes this example of producer-consumer under "More Monitor methods".

By the way, a true producer-consumer pattern revolves around a collection of work data, with one or more threads producing work by adding data to that collection, while one or more other threads consumer work by removing data from that collection. In our variation above, the "work data" is merely a count of the number of times we need to immediately check the feed.

(Another way to do it, instead of having the timer callback call Consume, is for the timer callback to lock and pulse a Monitor that Consume waits on. In that case, Consume has an infinite loop, like while(true), which you kick off one time in its own thread. Therefore there is no need to support re-entrancy with the call to Monitor.TryEnter.)

gWiz
Can you point me to good example of this pattern? Thanks.
R.Gregory
I've updated my answer to include a link to an example.
gWiz
By the way, welcome to StackOverflow. Don't forget to up-vote answers that you find helpful.
gWiz
Thank you. Your answer was very usefull. I can't vote until i have 15 reputation :(
R.Gregory
ok now you do ;)
qntmfred
A: 

you could try creating a main application that would contain the GUI you wouldn't want to freeze. it would start a thread with a loop that would iterate after a specific amount of time. if the timer equals/exceeds set time, start another thread for the request. But, check if a thread already exists before starting. If not, then block execution until the request thread is done. 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
}

Note: Don't do it every second. As other posters have said, it is a little disrespectful to the site owners. You risk getting denied access to the feed by the website owners.

maranas
+1  A: 

Use a timer like this:

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

The elapsed event is handled on a ThreadPool thread, so you will have to keep this in mind if you need to update the UI from the elapsed handler.

ebpower
Won't this wait 1 second after the request is complete instead of firing immediately if the request was longer than 1 second?
Blankasaurus
@CccTrash: Oops - I misread your question. I edited the TimerHandler to check repeatedly until the elapsed time drops below one second, then adjust the timer to fire one second after the last timer fired.
ebpower