我在.NET 4.5中构建一个小型stat客户端库,需要收集队列中的数据,然后通过发送一些HTTP消息定期处理队列中的数据。我正在寻找关于实现此代码周期部分的最佳方法的一些指导。
我认为使用ConcurrentQueue类最有意义来保存数据,客户端类的简化版本如下所示:
public class StatClient
{
private static readonly ConcurrentQueue<Stat> Stats = new ConcurrentQueue<Stat>();
public void RecordCount(string name, long count)
{
Stats.Enqueue(new CounterStat(name, count));
}
}
那么从队列中读取的每10秒触发后台进程的首选方法是什么?我应该只使用System.Timers.Timer
然后启动一个新的线程或任务,或者在启动时创建一个后台线程并使其休眠10秒,或者???
答案 0 :(得分:2)
我建议你使用BlockingCollection<stat>而不是ConcurrentQueue<Stat>
。而不是有一个为队列提供服务的定期任务,只需要一个不断查看队列的消费者线程。例如:
BlockingCollection<Stat> _stats = new BlockingCollection<Stat>();
public void RecordCount(string name, long count)
{
Stats.Add(new CounterStat(name, count));
}
您的消费者看起来像这样:
public void Consumer()
{
foreach (var stat in _stats.GetConsumingEnumerable())
{
// process stat
}
}
您可以将消费者作为线程或任务启动。作为一项任务,例如:
Task consumer = Task.Factory.StartNew(Consumer, TaskCreationOptions.LongRunning);
消费者将阻止,等待将某些内容添加到队列中。一旦添加了某些内容,消费者就会出列并处理它。
要停止消费者,请致电_stats.CompleteAdding()
。这将表明不再有任何项目添加到队列中。消费者将清空队列,然后退出循环。
如果你真的想要使用周期性任务,那么我建议使用计时器。但是让计时器一次性完成,这样你就不能进行并发调用。每次打勾后你都会重置计时器。像这样:
// create the timer
System.Timers.Timer timer = new Timer();
timer.Elapsed += TimerTick;
timer.Interval = 10000;
timer.AutoReset = false; // makes a one-shot timer rather than a periodic timer
timer.Enabled = true;
void TimerTick(object sender, EventArgs e)
{
// do something with the queue
// then reset the timer
timer.Enabled = true;
}
无论您采用哪种方式,我强烈建议您使用BlockingCollection
代替ConcurrentQueue
。 BlockingCollection
的API更容易使用,包括非繁忙等待,GetConsumingEnumerable
等好东西。更强大。
答案 1 :(得分:0)
我认为如你所说,有一个后台进程可以正常工作。在我看来,这并不像定时器引发事件和每次启动单独的后台进程那么复杂,因为如果先前生成的进程与新进程重叠并且正在争取访问资源,那么你可能遇到并发问题 - 你的{ {1}}。当然,您可以始终使用互斥锁来协调对该资源的访问,但另一方面则不那么复杂。