监视器、锁定或者易失性内存?

5
我有一个Windows服务(.NET 4),定期处理队列,例如每15分钟。我使用一个 System.Threading.Timer,在服务启动时设置它以每X毫秒触发回调。通常,每次运行需要几秒钟,并且从不冲突,但是如果我不能假设这一点 - 那么如果正在进行处理,则希望下一次运行立即退出。
这可以很容易地通过锁定、易失性布尔值或监视器来解决,但实际上在这种情况下应该使用什么,或者通常情况下的首选选项是什么?
我已经找到了其他帖子,几乎回答了这种情况(例如Volatile vs. Interlocked vs. lock),但需要一些关于将其扩展到具有立即退出的Timer示例的建议。
3个回答

6

对于这个问题,您不需要使用任何锁,只需在计时器委托内部重新安排下一个计时器执行即可。这将确保100%不会发生重叠。

在计时器的事件处理程序结束时调用 timer.Change(nextRunInMilliseconds,Timeout.Infinite),这样计时器将仅在 nextRunInMilliseconds 之后触发一次。

示例:

//Object that holds timer state, and possible additional data
private class TimerState
{
    public Timer Timer { get; set; }
    public bool Stop { get; set; }
}

public void Run()
{
    var timerState = new TimerState();
    //Create the timer but don't start it
    timerState.Timer = new Timer(OnTimer, timerState, Timeout.Infinite, Timeout.Infinite);
    //Start the timer
    timerState.Timer.Change(1000, Timeout.Infinite);
}

public void OnTimer(object state)
{
    var timerState = (TimerState) state;            
    try
    {
        //Do work
    }
    finally 
    {
        //Reschedule timer
        if (!timerState.Stop)
            timerState.Timer.Change(1000, Timeout.Infinite);
    }
}

1
最好在一开始就避免重叠。但是这是否应该与在处理开始时完全禁用计时器相结合,使用timer.Change(Timeout.Infinite, Timeout.Infinite) - anton.burger
@shambulator:在事件处理程序开始时禁用计时器,我赞成。通常,在事件处理程序的开头和处理周期结束后,可以使用“timer.Change(Timeout.Infinite, Timeout.Infinite)”将计时器恢复到其初始状态。 - now he who must not be named.
2
@shambulator:在处理开始时,计时器已被禁用。将Timeout.Infinite作为period参数传递将导致计时器仅触发一次,因此一旦委托触发,计时器就已经被禁用 - Boris B.
@BorisB。啊,那就有道理了 :) 我的错误在于假定计时器最初是设置为重复,但您的编辑清楚地表明它最初是单次计时器,并在每个间隔过去后进行更新。 - anton.burger

1

好的,任何一个都可以完成工作。通过lock使用通常很简单,但是在这种情况下你不能使用lock,因为你需要指定零超时; 因此,最简单的方法可能是使用CompareExchange

private int isRunning;
...
if(Interlocked.CompareExchange(ref isRunning, 1, 0) == 0) {
    try {
        // your work
    } finally {
        Interlocked.Exchange(ref isRunning, 0);
    }
}

Monitor做同样的事情的方法是:
private readonly object syncLock = new object();
...
bool lockTaken = false;
try {
    Monitor.TryEnter(syncLock, 0, ref lockTaken);
    if (lockTaken) {
        // your work
    }
} finally {
    if(lockTaken) Monitor.Exit(syncLock);
}

0

我认为,如果你发现需要同步计时器委托 - 你做错了,而且Timer可能不是你想要使用的类。在我看来,更好的方法是:

1)要么保留Timer,但将间隔值增加到安全假定不会出现线程问题的程度,

2)要么删除Timer并改用简单的线程。你知道,像这样的东西:

var t = new Thread();
t.Start(() =>
         {
            while (!_stopEvent.WaitOne(100))
            {
                 ..........
            }
         });

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接