2012-05-04 29 views
15

那你覺得是爲了防止同時運行的C#Windows服務的多線程的最佳方法(該服務使用與OnElapsed事件timer)?防止多個服務實例 - 最佳方法?

使用lock()mutex

我似乎無法理解mutex的概念,但使用lock()似乎對我的情況正常工作。

我應該花時間學習如何使用mutex反正?

+0

當你說「服務的多個實例」 - 你的意思是多個進程(即有人啓動程序的兩個副本),還是你的意思是在同一個應用程序中? – 2012-05-04 01:04:12

+0

那麼,OnElapsed事件將啓動實際執行該工作的void函數的新線程。有時候,這項工作可能會比定時器運行的時間間隔更長。這就是我的意思。 –

+1

請不要在「C# - 」等前加上標題。這就是標籤的用途。 –

回答

50

使您的計時器成爲一次性,並在已過去的事件處理程序中重新初始化它。例如,如果你使用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

你能用System.Threading.Timer嗎? (沒有設置AutoReset = false的可能性) – flagg19

+2

@ flagg19:如果使用'System.Threading.Timer',則使用最後一個參數初始化爲'Timeout.Infinite'。在回調函數中,你會調用'Change',最後一個參數等於Timeout.Infinite。 –

+1

您可以在計時器開始時使用秒錶,然後測量已用時間。然後根據時間延遲得到一個相當均勻的計時器。如果你的工作花費的時間超過了計時器之間所需的時間長度,那麼你的延遲將是0.如果花了半秒鐘,那麼你會延遲半秒,等等。 – KingOfHypocrites

2

如果你想要的是爲了防止兩個線程在同一進程/應用程序域從併發執行,該lock聲明可能會爲你做。

但是請注意,lock離開其他線程,那麼,鎖定而他們等待進入臨界區。他們不會被中止或重定向或任何事情;他們坐在那裏,等待原始線程完成lock塊的執行,以便它們可以運行。

一個mutex會給你更大的控制權;包括讓第二個和後續線程完全停止的能力,而不是鎖定和鎖定跨進程的線程。

9

不要使用定時器來生成線程。只有啓動一個線程。線程完成一個工作週期後,計算在下一個週期開始之前剩餘的時間。如果此間隔爲0或負數,則立即循環並開始一個新的循環,如果爲肯定的,則在循環返回前休眠該間隔。

這通常是通過採取之間完成一個unsigned 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); 
    } 
} 
+0

非常有用,謝謝。 –

+1

非常有趣。 **推薦:**將此改爲***總是*調用「睡眠」**。因此,最後一行,而不是'if..Sleep..'成爲'Thread.Sleep(Math.Max(remainingTicks,MINIMUM_TICKS);'。原因1:總是產生,以防有另一個線程在同一個優先級**。可以設置MINIMUM_TICKS = 0來得到一個短暫的收益率原因#2:避免消耗**太多的CPU **如果這個任務反覆花費太長時間,在這種情況下,MINIMUM_TICKS被選擇爲我們想要的時間量以保證這個任務不會在每個循環中執行任何cpu – ToolmakerSteve

+0

注意:不需要對'uint'進行轉換,'int' math「以這種方式包裝」,唯一需要的時間是開始和結束時間之間的差異超過int.MaxValue。(在這種情況下,你應該使用'ulong'和'GetTickCount64')。事實上,使用'uint'使得減法相當奇怪:你將兩個無符號的值,儘管結果可能是負值,這是無意義的,它只有在你把它轉換回int時纔有效。首先在'int'中。無論哪種方式,都必須允許「溢出」。 – ToolmakerSteve

1

我想我知道你在做什麼。你有一個定時器定期執行回調(定時器的定義),並且回調做了一些工作。該工作實際上可能比計時器週期花費更多時間(例如,計時器週期爲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 
}); 
4

而不是可以使用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); 
     } 
    } 
} 
+0

這是一個很好的解決方案,當你想完全扔掉「額外」執行。一個潛在的缺點是它增加了執行之間可能傳遞的最大時間(與其他解決方案相比)。這是好還是壞取決於執行超過計時器週期時想要發生的事情。如果你希望下一次執行在這種情況下很快開始,我推薦[Martin Jame的回答](https://stackoverflow.com/a/10441554/199364)。 – ToolmakerSteve

+0

...或[Tony Valenti的答案](https://stackoverflow.com/a/31466056/199364),帶有'IntervalStartTime.ElapsedStart'。 – ToolmakerSteve

1

我認爲,一些這些方法非常棒,但有點複雜。

我創建了一個包裝類,可以防止計時器重疊,並允許您選擇「ELAPSED是否應該每INTERVAL調用一次」或「應在兩次調用之間發生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 

    } 
} 
+0

Upvoted - 我喜歡:1)如果執行超過時間間隔,則在1毫秒後重新啓動該選項。 2)能夠在兩種方法之間進行選擇。 3)使它成爲一個包裝類。 4)使用'e.SignalTime',以便時間測量準確。 FWIW,我會將處理程序命名爲「OnElapsed」或「ElapsedHandler」而不是「Elapsed」(這聽起來像是一種時間測量)。 – ToolmakerSteve