17

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

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

4

5 に答える 5

47

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

 private void CreatorLoop(object state) 
 {
   if (Monitor.TryEnter(lockObject))
   {
     try
     {
       // Work here
     }
     finally
     {
       Monitor.Exit(lockObject);
     }
   }
 }
于 2009-11-09T07:37:10.497 に答える
6

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

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

元のタイマー オブジェクトのメソッドを使用して、新しいオブジェクトを破棄/作成せずにオプション #2 を管理できる場合がありますが、最初のタイムアウトが期限切れになった後に新しい開始タイムアウトChange()で呼び出す動作が正確に何であるかはわかりません。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);
}
于 2009-11-09T07:38:38.723 に答える
0

私はアトミック操作を提供する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;
        }

    }
于 2012-06-05T21:39:25.477 に答える
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
    // 
于 2014-09-18T09:48:49.657 に答える
0

経過したイベントがスレッドプールから実行され、再入可能である必要がある 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)            
于 2009-11-09T07:34:59.573 に答える