使用.NET 4.5定期执行事件

使用.NET 4.5定期执行事件,.net,multithreading,timer,.net,Multithreading,Timer,我正在.NET4.5中构建一个小型stat客户端库,它需要收集队列中的数据,然后通过发送一些HTTP消息定期处理队列中的数据。我正在寻找一些关于实现此代码周期部分的最佳方法的指导 我认为使用ConcurrentQueue类保存数据最有意义,客户端类的简化版本如下所示: public class StatClient { private static readonly ConcurrentQueue<Stat> Stats = new ConcurrentQueue<St

我正在.NET4.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));
    }
}
public void Consumer()
{
    foreach (var stat in _stats.GetConsumingEnumerable())
    {
        // process stat
    }
}
公共类StatClient
{
私有静态只读ConcurrentQueue Stats=new ConcurrentQueue();
public void RecordCount(字符串名称、长计数)
{
Stats.Enqueue(新计数器(名称、计数));
}
}

那么,每10秒触发一个后台进程并从队列中读取的首选方法是什么?我应该使用一个
系统.Timers.Timer
然后启动一个新的线程或任务,还是在启动时创建一个后台线程并让它休眠10秒,或者???

我认为,按照您的建议,让一个后台进程休眠是可行的。在我看来,这比每次使用计时器引发事件并启动单独的后台进程要简单得多,因为如果以前生成的进程与新进程重叠,并且正在争夺对资源(您的
ConcurrentQueue
)的访问权,您可能会遇到并发问题。诚然,您可以始终使用互斥来协调对该资源的访问,但另一种方式不那么复杂。

我建议您使用而不是
ConcurrentQueue
。与其让周期性任务服务于队列,不如让使用者线程不断地查看队列。例如:

BlockingCollection<Stat> _stats = new BlockingCollection<Stat>();

public void RecordCount(string name, long count)
{
    Stats.Add(new CounterStat(name, count));
}
Task consumer = Task.Factory.StartNew(Consumer, TaskCreationOptions.LongRunning);
您可以将消费者作为线程或任务启动。作为一项任务,例如:

BlockingCollection<Stat> _stats = new BlockingCollection<Stat>();

public void RecordCount(string name, long count)
{
    Stats.Add(new CounterStat(name, count));
}
Task consumer = Task.Factory.StartNew(Consumer, TaskCreationOptions.LongRunning);
消费者将阻塞,等待向队列中添加内容。一旦添加了某些内容,消费者将退出队列并对其进行处理

要停止使用者,请调用
\u 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使用起来容易得多,它包含了一些很好的功能,如非忙等待、
getConsumineUnimerable
等。这些功能更加强大。

但是,如果使用休眠线程,则除了在其唤醒时,无法干净地退出线程。必须等待10秒线程才能醒来,这样它才能退出,这是一种痛苦。而且真的没有理由在大部分时间都在睡觉的东西上烧线。仅仅使用一个在每次滴答声后重新启用的一次性计时器并不复杂。这可以防止在不需要持久线程的情况下重新进入。我使用了System.Timers.Timer路由。在我的场景中,我并不希望立即处理这些项目,而是希望每10秒对它们进行一次批处理。因此,AutoReset=false在这种情况下工作得很好。不必每10秒触发一次,只要在前一个滴答事件计时器完成后每10秒触发一次即可。