2012-05-04 7 views
15

C#Windowsサービスの複数のスレッドが同時に実行されないようにするにはどうすればよいですか(サービスはOnElapsedイベントでtimerを使用しています)。サービスの複数のインスタンスを防ぐ - 最善の方法?

lock()またはmutexを使用していますか?

私はmutexのコンセプトを把握していないようですが、lock()を使用すると私の場合は正常に動作するようです。

とにかくmutexの使い方を学ぶべきでしょうか?

+0

あなたは、「サービスの複数のインスタンス」と言う - あなたが複数のプロセスを意味しています(すなわち誰かがプログラムの2つのコピーを開始します)、またはあなたは、同じアプリケーションで意味ですか? –

+0

さて、OnElapsedイベントは、実際に作業を行っているvoid関数の新しいスレッドを起動します。時には、この作業は、タイマーが実行される間隔よりも終了する時間が長くなることがあります。それは私が意味していたものです。 –

+1

タイトルに「C# - 」などのプレフィックスを付けないでください。それがタグのためのものです。 –

答えて

50

タイマーをワンショットにして、elapsedイベントハンドラで再初期化します。

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 
} 

欠点は、タイマーが起動しないということであるあなたがSystem.Timers.Timerを使用している場合たとえば、あなたはこのようにそれを初期化したいです1秒間隔で。むしろ、最後のティックの処理が終了した後に1秒間点火します。

+0

System.Threading.Timerでもこれを行うことができますか? (AutoReset = falseを設定することはできません) – flagg19

+2

@ flagg19: 'System.Threading.Timer'を使用する場合は、最後のパラメータを' Timeout.Infinite'に初期化します。コールバックでは、最後のパラメータを 'Timeout.Infinite 'にして' Change'を呼び出します。 –

+1

タイマーの冒頭でストップウォッチを使用して、経過時間を測定することができます。次に、経過時間に基づいて、かなり偶数のタイマーを得るために遅延します。あなたの仕事がタイマーの間の希望の時間よりも長い時間を要した場合、あなたの遅れは0になります。半分かかったら、半分の時間を遅らせます。 – KingOfHypocrites

2

同じプロセス/アプリケーションドメインの2つのスレッドが同時に実行されないようにするには、lockステートメントがおそらく役に立ちます。

しかし、lockは、他のスレッドを去ります。は、クリティカルセクションへのアクセスを待つ間にをロックします。彼らは打ち切られたり、リダイレクトされたりしません。元のスレッドがlockブロックの実行を完了するのを待って、そこで実行されるように、そこに座っています。

mutexとすると、プロセス間でスレッドをロックしてロックするのではなく、2番目以降のスレッドを完全に停止させることができます。

9

スレッドを生成するためにタイマーを使用しないでください。 1つのスレッドだけを開始します。スレッドが作業サイクルを終了したら、次のサイクルが開始されるまでの残り時間を計算します。この間隔が0または負の場合は、すぐにループバックし、新しいサイクルを開始します。正の場合は、その間隔でスリープしてからループバックします。

これは通常、仕上がりのティックとスタートのティックの間の符号なしint減算のint結果を取ることによって行われます。これを所望の間隔から引くと、新しい時間が残る。

追加のタイマースレッドは必要ありません。同時に2つのスレッドを実行する可能性がなく、全体的なデザインが単純化され、継続的な作成/開始/終了/破棄はありません。malloc、no new()、no stack allocate/deallocate、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

非常に興味深い。 **推奨:**これを***に変更すると、常に「スリープ」**が呼び出されます。理由#1:同じ優先度の別のスレッドがある場合には必ず降伏します(つまり、スレッドがスリープ状態になっている場合は、理由は#2:このタスクが繰り返し時間がかかり過ぎるとCPUが多すぎるのを避けることができます。その場合、MINIMUM_TICKSは必要な時間に選択されます – ToolmakerSteve

+0

注: 'uint'へのキャストは必要ありません。' int'算術演算は、このような方法で「ラップ」します。これが必要な唯一の時間は、実際には、 'uint'を使用すると、減算がむしろ奇妙になります:あなたは2つの違いを取っています。(例:' ulong'と 'GetTickCount64'を使うべきです)あなたがそれを '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

これは素晴らしいソリューションです。 "余分な"実行を破棄します。 1つの潜在的な欠点は、(他のソリューションと比較して)実行の間に通過する可能性のある最大時間を増加させることです。これが良いか悪いかは、実行がタイマー時間を超えたときに何をしたいかによって決まります。このような場合にすぐに次の実行を開始したい場合は、[Martin Jameの答え](https://stackoverflow.com/a/10441554/199364)をお勧めします。 – ToolmakerSteve

+0

...または[Tony Valentiの回答](https://stackoverflow.com/a/31466056/199364)で、 'IntervalStartTime.ElapsedStart'を指定してください。 – ToolmakerSteve

1

私はいくつかのことを考えますこれらのアプローチは素晴らしいですが、少し複雑です。

タイマーが重複しないようにするラッパークラスを作成しました。「ELAPSEDをINTERVALごとに1回呼び出すかどうか」または「呼び出しの間に間隔を延ばす」を選択できます。

このコードで改善する場合は、ここに更新を投稿してください!

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)2つのアプローチのどちらかを選択できる。 3)ラッパークラスにする。 4) 'e.SignalTime'を使って、時間測定が正確です。 FWIW、私は 'Elapsed'ではなくOnElapsedまたはElapsedHandlerという名前をつけています(これは私には時間測定のように聞こえる)。 – ToolmakerSteve