Reputation: 131
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.
Upvotes: 10
Views: 5981
Reputation: 9
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();
}
Upvotes: 0
Reputation: 117010
I think that easiest way to deal with this now is to use the Microsoft Reactive Framework (Rx).
So, assuming I have a function that will call the website feed and return a Response
object, like this:
Func<Response> checkWebsiteFeed = ...;
Then I can just do this to wire it all up:
var query =
Observable
.Interval(TimeSpan.FromSeconds(1.0))
.Select(x => checkWebsiteFeed()) ;
query
.ObserveOnDispatcher()
.Subscribe(x =>
{
/* do something with response on UI thread */
});
The checkWebsiteFeed
is called on a background thread and the Subscribe
is run on the UI thread. Super simple!
Upvotes: 0
Reputation: 7426
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)) 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
);
}
}
}
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
.)
Upvotes: 9
Reputation: 1416
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.
Upvotes: 0
Reputation: 8531
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.
Upvotes: 1
Reputation: 62909
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.
Upvotes: 4
Reputation: 48392
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.
Upvotes: 1