2009-11-09 10 views
16

10 msごとに適切なイベントハンドラ(コールバック)を呼び出すSystem.Threading.Timerがあります。メソッド自体はであり、再入可能でないであり、時にはを10 msより長く取ることができます。したがって、メソッドの実行中にタイマーを停止したい。コールバックメソッドでタイマーを停止

コード:

private Timer _creatorTimer; 

// BackgroundWorker's work 
private void CreatorWork(object sender, DoWorkEventArgs e) { 
     _creatorTimer = new Timer(CreatorLoop, null, 0, 10); 

     // some other code that worker is doing while the timer is active 
     // ... 
     // ... 
} 

private void CreatorLoop(object state) { 
     // Stop timer (prevent reentering) 
     _creatorTimer.Change(Timeout.Infinite, 0); 

     /* 
      ... Work here 
     */ 

     // Reenable timer 
     _creatorTimer.Change(10, 0); 
} 

MSDNには、コールバックメソッドは、スレッドプールとは別のスレッドで(毎回タイマーがトリガー)と呼ばれていると述べています。つまり、メソッドの最初のものをタイマーで止めても、タイマーを停止させる機会がある前に、メソッドの別のインスタンスを起動して実行する必要はありません。

タイマー(または非リエントラントメソッド自体)をロックする必要がありますか? コールバック(および非リエントラント)メソッドの実行中にタイマーが起動しないようにする正しい方法は何ですか?

+0

この質問はあなたを助けるかもしれhttp://stackoverflow.com/questions/1116249/manualresetevent-vs-thread-sleep – Kane

答えて

45

タイマーにコールバックメソッドの実行を継続させますが、非リエントラントコードをMonitor.TryEnter/Exitでラップすることができます。タイマーを停止/再起動する必要はありません。オーバーラップしているコールはロックを取得せず、ただちにリターンします。

private void CreatorLoop(object state) 
{ 
    if (Monitor.TryEnter(lockObject)) 
    { 
    try 
    { 
     // Work here 
    } 
    finally 
    { 
     Monitor.Exit(lockObject); 
    } 
    } 
} 
+0

+1 TryEnterの使用を考えたことはありませんでした。それはとても面白いです。 – Schmuli

+0

これはやっているようです。 2つ以上のスレッドがメソッドに入るかもしれませんが、実際には1つのみが動作します。また、Monitor.TryEnter()の後にタイマーを停止していますので、実行時間がタイマーの期間よりもはるかに長い場合に備えて、実行中には起動しません(起動する必要はありません)。タイマーは、作業中の作業が完了した後に再開されます。 –

+0

+1すてきな解決策! – ParmesanCodice

0

私はSystem.Timers.Timerと同様の状況にありました。ここで、elapsedイベントはスレッドプールから実行され、リエントラントにする必要があります。

私はこの問題を回避するために、このメソッドを使用:

private void tmr_Elapsed(object sender, EventArgs e) 
{ 
    tmr.Enabled = false; 
    // Do Stuff 
    tmr.Enabled = true; 
} 

何をやっているあなたがでSystem.Timers.Timerを検討する必要がありますに応じて、ここにMSDN

          System.Windows.Forms System.Timers   System.Threading 
Timer event runs on what thread?   UI thread    UI or worker thread Worker thread 
Instances are thread safe?    No      Yes     No 
Familiar/intuitive object model?   Yes      Yes     No 
Requires Windows Forms?     Yes      No     No 
Metronome-quality beat?     No      Yes*     Yes* 
Timer event supports state object?  No      No     Yes 
Initial timer event can be scheduled? No      No     Yes 
Class supports inheritance?    Yes      Yes     No 

* Depending on the availability of system resources (for example, worker threads)    
からの素敵な要約です
+0

私はこのことを信じています実際には問題を回避しません。 99.9%の場合がありますが、次のタイマーのElapsedイベントの発生前にイベントハンドラにプロセッサ時間を与えないと、2つの異なるスレッドが同時にメソッドを実行することがあります。 –

+0

良い点!あなたはいつもjswのようなロックソリューションと一緒にこれを使用することができます – ParmesanCodice

6

カップル可能な解決策:

  • は、イベントに待っているさらに別のスレッドのデリゲートで行われ、実際の仕事を持っています。タイマーコールバックは単にイベントを通知します。ワーカースレッドは、イベントが通知されたときにのみ作業を行う単一のスレッドであるため、再入力することはできません。タイマーはリエントラントなので、イベントに信号を送るだけです(少し回転して無駄に見えますが、うまくいくでしょう)
  • 開始タイムアウトと周期的タイムアウトなしでタイマーを作成して、 。タイマーコールバックはそのタイマーオブジェクトを破棄し、そのタイマーオブジェクトが完了すると新しいタイマーオブジェクトを作成します。

あなたは、元のタイマーオブジェクトのChange()方法を使用して、新しいオブジェクトを作成/廃棄せずにオプション#2を管理することができるかもしれないが、私は行動が正確に新しいとChange()を呼び出すのが何であるかわからないんだけど最初のタイムアウトが終了した後にタイムアウトを開始します。それは1つか2つのテストに値するでしょう。

編集:


私がテストをしました - 再起動可能なワンショットは完璧に動作すると思われるようタイマーを操作し、それが他の方法よりもはるかに簡単です。アトミック操作を提供している私はインターロックでそれを行う

private Timer _creatorTimer; 

// BackgroundWorker's work 
private void CreatorWork(object sender, EventArgs e) { 
    // note: there's only a start timeout, and no repeat timeout 
    // so this will fire only once 
    _creatorTimer = new Timer(CreatorLoop, null, 1000, Timeout.Infinite); 

    // some other code that worker is doing while the timer is active 
    // ... 
    // ... 
} 

private void CreatorLoop(object state) { 
    Console.WriteLine("In CreatorLoop..."); 
    /* 
     ... Work here 
    */ 
    Thread.Sleep(3000); 

    // Reenable timer 
    Console.WriteLine("Exiting..."); 

    // now we reset the timer's start time, so it'll fire again 
    // there's no chance of reentrancy, except for actually 
    // exiting the method (and there's no danger even if that 
    // happens because it's safe at this point). 
    _creatorTimer.Change(1000, Timeout.Infinite); 
} 
+0

これはうまくいくと思われますが、メソッドの結果が多い場合(多くのif-else分岐と例外)、コードのクリーン性が低下します。しかし、同期メカニズムが使用されていないため、パフォーマンスに関しては良い解決策と思われます。これ以外に、このメソッドに入ることを試みることができる他のメソッド/スレッド/タイマーがある場合、これは動作しません。もちろん、モニターがうまく機能しないリエントラントでないメソッドに再入力しています。とにかく、ソリューションとテストをありがとう。良いアイデアです。 –

+0

コードの複雑さは、あなたがミューテックスを使用している場合よりも、もはや問題ではありません。単に 'try' /' finally'でコードをラップするか、複雑で複雑なルーチンを呼び出して、シンプルな '呼び出しをしてからタイマーをリセットします。コールバックが複数のタイマーによって使用される場合、はい、このテクニックは機能しません。真の同期オブジェクトが必要です。コールバックが非常に複雑な場合(特に、タイマーコールバックが非常に特定の目的のために設計されていることを意味する)、タイマーコールバックが複数のタイマーで使用されることは珍しいことです。 –

+0

あなたはSystem.Timers.Timerを使ってこれと同じ動作を達成できますが、これははるかに簡単です。 http://stackoverflow.com/questions/7055820/non-reentrant-timers/ –

0

、およびCompareExchangeであることを保証します:ここで出発点として、あなたに基づいていくつかのサンプルコードは、(いくつかの詳細が、それは私のマシン上でコンパイルするために取得するために変更されている場合があります)です同時に複数のスレッドがクリティカルセクションに入る:

private int syncPoint = 0; 

private void Loop() 
    { 
     int sync = Interlocked.CompareExchange(ref syncPoint, 1, 0); 
     //ensures that only one timer set the syncPoint to 1 from 0 
     if (sync == 0) 
     { 
      try 
      { 
       ... 
      } 
      catch (Exception pE) 
      { 
       ... 
      } 
      syncPoint = 0; 
     } 

    } 
0
//using Timer with callback on System.Threading namespace 
    // Timer(TimerCallback callback, object state, int dueTime, int period); 
    //  TimerCallback: delegate to callback on timer lapse 
    //  state: an object containig information for the callback 
    //  dueTime: time delay before callback is invoked; in milliseconds; 0 immediate 
    //  period: interval between invocation of callback; System.Threading.Timeout.Infinity to disable 
    // EXCEPTIONS: 
    //  ArgumentOutOfRangeException: negative duration or period 
    //  ArgumentNullException: callback parameter is null 

    public class Program 
    { 
     public void Main() 
     { 
      var te = new TimerExample(1000, 2000, 2); 
     } 
    } 

    public class TimerExample 
    { 
     public TimerExample(int delayTime, int intervalTime, int treshold) 
     { 
      this.DelayTime = delayTime; 
      this.IntervalTime = intervalTime; 
      this.Treshold = treshold; 
      this.Timer = new Timer(this.TimerCallbackWorker, new StateInfo(), delayTime, intervalTime); 
     } 

     public int DelayTime 
     { 
      get; 
      set; 
     } 

     public int IntervalTime 
     { 
      get; 
      set; 
     } 

     public Timer Timer 
     { 
      get; 
      set; 
     } 

     public StateInfo SI 
     { 
      get; 
      set; 
     } 

     public int Treshold 
     { 
      get; 
      private set; 
     } 

     public void TimerCallbackWorker(object state) 
     { 
      var si = state as StateInfo; 

      if (si == null) 
      { 
       throw new ArgumentNullException("state"); 
      } 

      si.ExecutionCounter++; 

      if (si.ExecutionCounter > this.Treshold) 
      { 
       this.Timer.Change(Timeout.Infinite, Timeout.Infinite); 
       Console.WriteLine("-Timer stop, execution reached treshold {0}", this.Treshold); 
      } 
      else 
      { 
       Console.WriteLine("{0} lapse, Time {1}", si.ExecutionCounter, si.ToString()); 
      } 
     } 

     public class StateInfo 
     { 
      public int ExecutionCounter 
      { 
       get; 
       set; 
      } 

      public DateTime LastRun 
      { 
       get 
       { 
        return DateTime.Now; 
       } 
      } 

      public override string ToString() 
      { 
       return this.LastRun.ToString(); 
      } 
     } 
    } 

    // Result: 
    // 
    // 1 lapse, Time 2015-02-13 01:28:39 AM 
    // 2 lapse, Time 2015-02-13 01:28:41 AM 
    // -Timer stop, execution reached treshold 2 
    // 
+0

コードフォーマッタなどを使ってそのコードを整理することをお勧めします。それはすばらしいかもしれませんが、最初の印象は数えられますし、私が最初に印象づけたのは、そういう見た目のコードを使いたくない場合です。 – ProfK

+0

これは速くても実用的な例でした。 – BTE

関連する問題