我的应用程序需要每秒检查一次网站提要。
有时对服务器的请求超过一秒钟。在这种情况下,应用程序需要等到第一个请求完成,然后立即启动新请求。我怎么能实现这个呢?
此外,请求不应冻结GUI。
答案 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();
}