我有一个Windows服务应用程序,它使用Threading.Timer
和TimerCallback
以特定间隔进行一些处理。我需要一次只将这个处理代码锁定到1个线程。
例如,启动服务并触发第一个回调并启动一个线程并开始处理。只要在下一次回调之前完成处理,这就可以正常工作。所以说比如说处理花费的时间比平时长一些,并且在另一个线程处理时再次触发TimerCallback,我需要让该线程等到另一个线程完成。
以下是我的代码示例:
static Timer timer;
static object locker = new object();
public void Start()
{
var callback = new TimerCallback(DoSomething);
timer = new Timer(callback, null, 0, 10000);
}
public void DoSomething()
{
lock(locker)
{
// my processing code
}
}
这是一种安全的方法吗?如果队列变得相当大,会发生什么?有更好的选择吗?
答案 0 :(得分:32)
如果你可以在它们之间以恒定的间隔触发事件(与以恒定间隔激发它们的当前代码相反),那么你可以在没有句点的情况下启动计时器,并且每次排队时新的回调,例如
static Timer timer;
public void Start()
{
var callback = new TimerCallback(DoSomething);
timer = new Timer(callback, null, 0, Timeout.Infinite);
}
public void DoSomething()
{
try
{
// my processing code
}
finally
{
timer.Change(10000, Timeout.Infinite);
}
}
此代码告诉新创建的计时器立即触发,仅一次。在处理代码中,它完成工作,然后告诉计时器在10秒内再次触发,仅一次。因为定时器现在不是定期触发,而是通过其回调方法重新启动,所以回调保证是单线程的,没有队列。
如果你想保持一个恒定的间隔,那么它有点棘手,因为你必须决定如果处理开始花费的时间超过定时器间隔该怎么办。一种选择是执行您当前正在执行的操作,但这实际上会导致大量排队的线程和最终的线程池饥饿。另一种选择是,如果已经有一个回调,则简单地丢弃回调,例如
static Timer timer;
static object locker = new object();
public void Start()
{
var callback = new TimerCallback(DoSomething);
timer = new Timer(callback, null, 0, 10000);
}
public void DoSomething()
{
if (Monitor.TryEnter(locker))
{
try
{
// my processing code
}
finally
{
Monitor.Exit(locker);
}
}
}
答案 1 :(得分:1)
如果处理代码执行时间超过10秒,最糟糕的情况是,每次调用一个新的回调时,你将浪费一个线程池线程(它们将在lock语句中等待)。如果你把所有的线程池线程HttpWebRequest,ASP.NET,异步委托调用......都会受到影响。
我要做的是立即安排第一次回调。然后,如果你真的需要每10秒调用一次 DoSomething():
public void DoSomething ()
{
DateTime start = DateTime.UtcNow;
...
TimeSpan elapsed = (DateTime.UtcNow - start);
int due_in = (int) (10000 - elapsed.TotalMilliseconds);
if (due_in < 0)
due_in = 0;
timer.Change (due_in, Timeout.Infinite);
}
或者那条线上的东西。