web-dev-qa-db-ja.com

コールバックメソッドでタイマーを停止する

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

コード:

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は、コールバックメソッドがスレッドプールとは別のスレッドで(タイマーが起動するたびに)呼び出されると述べています。つまり、メソッドの最初のことでタイマーを停止しても、最初のインスタンスがタイマーを停止する前に、タイマーが起動してメソッドの別のインスタンスを実行することを必ずしも妨げることはありません。

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

16
Kornelije Petak

タイマーにコールバックメソッドの起動を継続させることはできますが、再入可能でないコードをMonitor.TryEnter/Exitでラップします。その場合、タイマーを停止/再起動する必要はありません。重複する呼び出しはロックを取得せず、すぐに戻りません。

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

いくつかの可能な解決策:

  • イベントを待機しているさらに別のスレッドデリゲートで実際の作業を実行します。タイマーコールバックは、単にイベントを通知するだけです。ワーカースレッドは、イベントが通知されたときにのみ機能する単一のスレッドであるため、再入力できません。タイマーは再入可能です。これは、イベントを通知するだけなので(少し回り道で無駄に思えますが、機能します)
  • タイマーを開始タイムアウトのみで作成し、定期的なタイムアウトなしで作成して、1回だけ起動するようにします。タイマーコールバックは、そのタイマーオブジェクトを破棄し、作業が完了すると新しいオブジェクトを作成します。これも1回だけ起動します。

元のタイマーオブジェクトの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);
}
6
Michael Burr

System.Timers.Timerでも同様の状況が発生しました。この場合、経過したイベントはスレッドプールから実行され、再入可能である必要があります。

私はこの方法を使用して問題を回避しました:

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
ParmesanCodice

私はアトミック操作を提供するInterlockedでそれを行い、CompareExchangeによって、一度に1つのスレッドだけがクリティカルセクションに入ることを保証します。

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
ozba
    //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
BTE