防止多个服务实例 – 最佳方法?

那么,您认为如何防止同时运行C#Windows服务的多个线程(该服务正在使用带有OnElapsed事件的timer )?

使用lock()mutex

我似乎无法掌握mutex的概念,但使用lock()似乎对我的情况很好。

我应该花时间学习如何使用mutex吗?

让你的计时器成为一次性的,并在经过的事件处理程序中重新初始化它。 例如,如果你使用的是System.Timers.Timer ,你可以这样初始化它:

 myTimer.Elapsed = timer1Elapsed; myTimer.Interval = 1000; // every second myTimer.AutoReset = false; // makes it fire only once myTimer.Enabled = true; 

而你的流逝的事件处理程序:

 void timerElapsed(object source, ElapsedEventArgs e) { // do whatever needs to be done myTimer.Start(); // re-enables the timer } 

这样做的缺点是定时器不能以一秒为间隔触发。 而是在最后一个刻度的处理完成后触发一秒钟。

不要使用计时器来产生线程。 只有启动一个线程。 线程完成一个工作周期后,计算在下一个周期开始之前剩余的时间。 如果此间隔为0或负数,则立即循环并开始新的循环,如果为正数,则在循环返回之前休眠该间隔。

这通常是通过在结束刻度和开始刻度之间取一个无符号的int减法的int结果来完成的,所以给出了工作所耗费的刻度。 从期望的时间间隔中减去这个时间,剩下新的时间。

不需要额外的计时器线程,不能同时运行两个线程,简化了总体设计,没有连续的创建/开始/终止/销毁,没有mallocs,没有新的(),没有堆栈分配/释放,没有GC。

其他使用定时器,互斥锁,信号量,锁等的设计只是过于复杂。 为什么要试图阻止多余的线程与同步,如果它只是简单更容易,更简单的不做任何额外的线程?

有时,使用计时器而不是sleep()循环只是一个非常糟糕的主意。 这听起来像是其中的一次。

 public void doWorkEvery(int interval) { while (true) { uint startTicks; int workTicks, remainingTicks; startTicks = (uint)Environment.TickCount; DoSomeWork(); workTicks=(int)((uint)Environment.TickCount-startTicks); remainingTicks = interval - workTicks; if (remainingTicks>0) Thread.Sleep(remainingTicks); } } 

如果另一个计时器线程已经在执行回调,则可以使用Monitor.TryEnter()来返回而不是锁定

 class Program { static void Main(string[] args) { Timer t = new Timer(TimerCallback, null,0,2000); Console.ReadKey(); } static object timerLock = new object(); static void TimerCallback(object state) { int tid = Thread.CurrentThread.ManagedThreadId; bool lockTaken = false; try { lockTaken = Monitor.TryEnter(timerLock); if (lockTaken) { Console.WriteLine("[{0:D02}]: Task started", tid); Thread.Sleep(3000); // Do the work Console.WriteLine("[{0:D02}]: Task finished", tid); } else { Console.WriteLine("[{0:D02}]: Task is already running", tid); } } finally { if (lockTaken) Monitor.Exit(timerLock); } } } 

如果您只想阻止同一个进程/应用程序域中的两个线程同时执行,那么lock语句可能会为您执行。

但是请注意, lock等待访问关键部分时锁定其他线程, 锁定 。 他们不被中止或重定向或任何东西; 他们坐在那里,等待原始线程完成lock块的执行,以便它们可以运行。

一个mutex给你更大的控制; 包括第二个和后续线程完全停止的能力,而不是锁定和锁定跨进程的线程。

我想我知道你在做什么。 你有一个定时器定期执行回调(定时器的定义),回调做了一些工作。 这一点工作实际上可能需要比定时器周期更长的时间(例如定时器周期为500毫秒,并且给定的回调调用可能需要比500毫秒更长的时间)。 这意味着您的回调需要重新进入。

如果你不能重入(这可能有多种原因); 我过去所做的是在回调开始时关闭计时器,最后再打开计时器。 例如:

 private void timer_Elapsed(object source, ElapsedEventArgs e) { timer.Enabled = false; //... do work timer.Enabled = true; } 

如果你真的想要一个“线程”立即执行另一个,我不会建议使用一个计时器; 我会建议使用任务对象。 例如

 Task.Factory.StartNew(()=>{ // do some work }) .ContinueWith(t=>{ // do some more work without running at the same time as the previous }); 

我认为这些方法中的一些很棒,但有点复杂。

我创建了一个封装类来防止计时器重叠,并允许您选择“每隔INTERVAL应该调用一次ELAPSED”还是“在两次调用之间应该发生一次INTERVAL延迟”。

如果你改进这个代码,请在这里发布更新!

 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AllCommander.Diagnostics { public class SafeTimer : IDisposable { public enum IntervalStartTime { ElapsedStart, ElapsedFinish } private System.Timers.Timer InternalTimer; public bool AutoReset { get; set; } public bool Enabled { get { return InternalTimer.Enabled; } set { if (value) { Start(); } else { Stop(); } } } private double __Interval; public double Interval { get { return __Interval; } set { __Interval = value; InternalTimer.Interval = value; } } /// <summary> /// Does the internal start ticking at the END of Elapsed or at the Beginning? /// </summary> public IntervalStartTime IntervalStartsAt { get; set; } public event System.Timers.ElapsedEventHandler Elapsed; public SafeTimer() { InternalTimer = new System.Timers.Timer(); InternalTimer.AutoReset = false; InternalTimer.Elapsed += InternalTimer_Elapsed; AutoReset = true; Enabled = false; Interval = 1000; IntervalStartsAt = IntervalStartTime.ElapsedStart; } void InternalTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { if (Elapsed != null) { Elapsed(sender, e); } var ElapsedTime = DateTime.Now - e.SignalTime; if (AutoReset == true) { //Our default interval will be INTERVAL ms after Elapsed finished. var NewInterval = Interval; if (IntervalStartsAt == IntervalStartTime.ElapsedStart) { //If ElapsedStart is set to TRUE, do some fancy math to determine the new interval. //If Interval - Elapsed is Positive, then that amount of time is remaining for the interval //If it is zero or negative, we're behind schedule and should start immediately. NewInterval = Math.Max(1, Interval - ElapsedTime.TotalMilliseconds); } InternalTimer.Interval = NewInterval; InternalTimer.Start(); } } public void Start() { Start(true); } public void Start(bool Immediately) { var TimerInterval = (Immediately ? 1 : Interval); InternalTimer.Interval = TimerInterval; InternalTimer.Start(); } public void Stop() { InternalTimer.Stop(); } #region Dispose Code //Copied from https://lostechies.com/chrispatterson/2012/11/29/idisposable-done-right/ bool _disposed; public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } ~SafeTimer() { Dispose(false); } protected virtual void Dispose(bool disposing) { if (!_disposed) { if (disposing) { InternalTimer.Dispose(); } // release any unmanaged objects // set the object references to null _disposed = true; } } #endregion } }