2011-02-02 49 views
10

標準System.Timers.Timerの動作に問題があります。タイマーは、一定間隔でElapsedイベントを発生させます。しかし、Elapsedイベントハンドラ内の実行時間がタイマーインターバルを超えると、スレッドプールはイベント処理をキューに入れ始めます。これは私の場合の問題です。これは、私のElapsedイベントハンドラでは、データベースから何らかのデータを取得して何かを実行し、最後に結果をデータベースに戻すためです。しかし、データの取り扱いは一度だけ行う必要があります。したがって、System.Timers.Timerの経過イベントをキューに入れないようにする方法があります。出力は、としてここになりますSystem.Timers.Timerがスレッドプール上で実行するためのキューイングを防止する方法

public class EntryPoint 
{ 

    private static void TimeProc(object state, ElapsedEventArgs e) 
    { 
     Console.WriteLine("Current time {0} on the thread {1}", DateTime.Now, Thread.CurrentThread.ManagedThreadId); 
     Thread.Sleep(20000); 
    } 

    static void Main(string[] args) 
    { 
     Console.WriteLine("Press <Enter> for finishing\n\n"); 
     ThreadPool.SetMaxThreads(10, 10); 
     System.Timers.Timer MyTimer = new System.Timers.Timer(1000); 
     MyTimer.Elapsed += new ElapsedEventHandler(TimeProc); 
     MyTimer.Start(); 
     Console.ReadLine(); 
     MyTimer.Stop(); 
    } 
} 

そして可能:

Current time 03.02.2011 0:00:09 on the thread 4 
Current time 03.02.2011 0:00:10 on the thread 5 
Current time 03.02.2011 0:00:12 on the thread 6 
Current time 03.02.2011 0:00:13 on the thread 7 
Current time 03.02.2011 0:00:14 on the thread 8 
Current time 03.02.2011 0:00:15 on the thread 9 
Current time 03.02.2011 0:00:16 on the thread 10 
Current time 03.02.2011 0:00:17 on the thread 11 
Current time 03.02.2011 0:00:18 on the thread 12 
Current time 03.02.2011 0:00:19 on the thread 13 
Current time 03.02.2011 0:00:30 on the thread 4 
Current time 03.02.2011 0:00:30 on the thread 5 

考えられる解決策:この問題の実例として、あなたは、次のテストプログラムを検討することができ

1)それは、次の要因によって影響を受けました:C# Timer vs Thread in Service

そして、ここでのサンプル上記に係るようなコードがあります

public class EntryPoint 
    { 
     private static System.Timers.Timer MyTimer; 
     private static void TimeProc(object state, ElapsedEventArgs e) 
     { 
      Console.WriteLine("Current time {0} on the thread {1}", DateTime.Now, Thread.CurrentThread.ManagedThreadId); 
      Thread.Sleep(20000); 
      MyTimer.Enabled = true; 
     } 

     static void Main(string[] args) 
     { 
      Console.WriteLine("Press <Enter> for finishing\n\n"); 
      ThreadPool.SetMaxThreads(10, 10); 
      MyTimer = new System.Timers.Timer(1000); 
      MyTimer.AutoReset = false; 

      MyTimer.Elapsed += new ElapsedEventHandler(TimeProc); 
      MyTimer.Enabled = true; 
      Console.ReadLine(); 

     } 
    } 

2)第2の方法がするSynchronizingObjectについてですが、それだけでWindowsフォームアプリケーションまたはその希望のオブジェクトを実装するためのコードの必要な追加開発のための価値がありますISynchronizeInvokeインターフェイスを実装する必要があります。あなたが見つけることができるこの方法の詳細here

したがって、私は最初の解決策を優先します。

+0

タイマーは問題ありません...これはすべての場所で発生しました。実際には、Elapsedイベントをキューに入れていることがうれしいです。これがハードウェアの割り込みだった場合は、あなたのインターバルを吹き飛ばし、あなたはそれを取り戻すことができません。 –

+0

データを1回だけ処理する必要がある場合は、繰り返すタイマーはなぜですか、またはX時間ごとに最大1回ということですか? – Chris

+0

@Chris私のケースでは、別のアプリケーションがデータベースにデータを保存し、アプリケーションがそのデータを読み込んで処理する必要があります。 – apros

答えて

14

私が通常この場合に行うことは、Elapsedハンドラの開始時にタイマを停止し、最後にもう一度起動することです。この方法では、一度に1つのティックしか処理しません。

UPDATE:MSDNのリンクあたり

、私はその意味することは、あなたがあなた自身のフラグを設定し(まだダニが登場する)ことができることだと思いますが、スレッドの安全対策にも注意が必要です。

+0

はい、あなたは正しいです。しかし、タイマーを止めて起動するための追加リソースが必要であったため、正しい方法ではないようです。 – apros

+1

@apros:発生している動作は仕様です。 1分ごとに30秒ごとにタイマーを設定すると、2回のティックイベントが発生すると予想されます。あなたが違う振る舞いをしたいのなら、これはそれを行う方法です。 –

+0

MSDN請求はこちら:http://msdn.microsoft.com/en-us/library/system.timers.timer.elapsed.aspx "この競合状態を解決する1つの方法は、イベントハンドラに経過したイベントは無視されます。 "ですから、この問題を解決する別の方法が存在するはずです。 – apros

4

私は単にそれを停止し、このような長い実行後にそれを開始すると言うでしょう。

tmr.Stop(); 
//Your lengthy execution code goes here 
tmr.Start(); 
+1

これは、私が一般的に行っていることですが、停止を使用するときにもこれを認識する必要があります。http://msdn.microsoft.com/en-us/library/system.timers.timer.stop.aspx黄色で始まる部分停止中に2回目の昼食からユーザに警告する。 – lollancf37

+0

@ lollancf37:それにはどんな利点がありますか? – Chris

+0

@Chrisあなたがスレッドではないと話しているのであれば、私は自分のサービサーで働いていましたが、他のイベントをキャプチャして実行コードを終了させなければなりませんでした。それが私がそれについて話し始めた理由です。 – lollancf37

4

表示されている動作は仕様です。タイマーにSynchronizingObjectを設定するか、複数のスレッドでチェックしない別のタイマー(System.Threading.Timerなど)を使用してください。

+0

System.Threading.Timerに関する情報は間違っています。 http://msdn.microsoft.com/en-us/library/system.threading.timer.aspx - 「タイマーによって実行されるコールバックメソッドは、ThreadPoolスレッドで呼び出されるため、再入可能にする必要があります。コールバックを実行できますタイマー間隔がコールバックの実行に必要な時間よりも短い場合、またはすべてのスレッドプールスレッドが使用中で、コールバックが複数回キューイングされている場合は、同時に2つのスレッドプールスレッドで実行されます。 おそらく、あなたはSystem.Windows.Forms.Timer – bkr

+0

を考えていたかもしれませんが、同期オブジェクトを設定することで、キューイングが妨げられることはありませんが、一度に1つずつ処理するように制限されます。キュー。動作の詳細については、タイマの比較についてhttp://msdn.microsoft.com/en-us/magazine/cc164015.aspxを参照してください。 – bkr

2

静的フラグ変数を作成するだけです。この方法で私のタイマーは動作し続けますが、メソッドが次のタイマーサイクルの前に完了していない場合、コードは単純にバイパスされます。

タイマーで使用される方法では、イベントが進行中かどうかをテストします。

Timer_Method_Called() 
{ 
    if (eventInProgress == 0) 
    { 
    // flag event as in progress 
    eventInProcess == 1; 

    // perform code.... 

    // after code is complete, allow the method to execute 
    eventInProgress == 0; 
    } 
} 
+4

これはスレッドセーフではなく、あなたがあなたの割り当て(=)であると仮定しているものに対して==を使用しています。 – bkr

2

の回答がいずれもスレッドセーフではないので、私は1提案してみましょう:それはまだ実行されていないかどうかを確認するためにタイマハンドラを最初にチェックし、

void oneHundredMS_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { 

    if (setTimerBodyRunning()) { //only proceed to body if it is not already processing; setTimerBodyRunning must be thread-safe 
    // here you do your long running operation 
    setTimerBodyFinished(); 
    } 
} 

あなたが見ることができるようにし、 falseが返された場合にのみ身体に進む。trueが返された場合、ハンドラはすぐに戻り、ティックはキューに入れません(シンプルなロック文が使用されているはずです)。ここでsetTimerBodyRunningの定義であり、setTimerBodyFinished:

private bool setTimerBodyRunning() { 
     bool retVal = false; 
     lock (timerBodyRunning) { //timerBodyRunning is type object and it holds a bool. 
      //The reason it is object and not bool is so it can be locked on to ensure thread safety 
      if (!((bool)timerBodyRunning)) { 
       timerBodyRunning = true; 
       retVal = true; 
      } 
     } 
     return retVal; 
    } 

private void setTimerBodyFinished() { 
    lock (timerBodyRunning) { 
     timerBodyRunning = false; 
    } 
} 

は、ここでは、タイマーを初期化し、起動したい方法は次のとおりです。

object timerBodyRunning = new object(); 
timerBodyRunning = false; 
System.Timers.Timer timerFrequency100MS = new System.Timers.Timer(); 
timerFrequency100MS.Interval = FREQUENCY_MS; //it will fire every 100 milliseconds 
timerFrequency100MS.Elapsed += new System.Timers.ElapsedEventHandler(oneHundredMS_Elapsed); 
timerFrequency100MS.Start(); 
+0

これはスレッドの安全性の問題を処理します。 finallyブロックにsetTimerBodyFinished呼び出しを置いて、常に呼び出されるようにしてください。 – user3112728

関連する問題