C#のSystem.Threading.Timerが機能していないようです。3秒ごとに非常に速く実行されます


112

タイマーオブジェクトがあります。毎分実行してほしい。具体的には、OnCallBackメソッドを実行する必要があり、メソッドの実行中に非アクティブになりOnCallBackます。一度OnCallBackメソッドが終了すると、それの(a OnCallBack)、タイマーを再起動します。

これが私が今持っているものです:

private static Timer timer;

private static void Main()
{
    timer = new Timer(_ => OnCallBack(), null, 0, 1000 * 10); //every 10 seconds
    Console.ReadLine();
}

private static void OnCallBack()
{
    timer.Change(Timeout.Infinite, Timeout.Infinite); //stops the timer
    Thread.Sleep(3000); //doing some long operation
    timer.Change(0, 1000 * 10);  //restarts the timer
}

ただし、動作していないようです。3秒ごとに非常に高速に実行されます。ピリオドを上げても(1000 * 10)。それは盲目になっているようです1000 * 10

何を間違えたのですか?


12
From Timer.Change:「dueTimeがゼロ(0)の場合、コールバックメソッドがすぐに呼び出されます。」それは私にはゼロのように見えます。
Damien_The_Unbeliever 2012年

2
はい、でも何ですか?期間もあります。
Alan Coromano、2012年

10
では、ピリオドもあるとしたらどうでしょう?引用された文は、期間の値についての主張をしません。「この値がゼロの場合は、すぐにコールバックを呼び出す」と表示されます。
Damien_The_Unbeliever

3
興味深いことに、dueTimeとperiodの両方を0に設定すると、タイマーは毎秒実行され、すぐに開始されます。
ケルビン

回答:


230

これは、System.Threading.Timerの正しい使用法ではありません。タイマーをインスタンス化するときは、ほとんどの場合、次のことを行う必要があります。

_timer = new Timer( Callback, null, TIME_INTERVAL_IN_MILLISECONDS, Timeout.Infinite );

これにより、タイマーは、間隔が経過したときに1回だけティックするように指示されます。次に、コールバック関数で、作業が完了する前ではなく完了後にタイマーを変更します。例:

private void Callback( Object state )
{
    // Long running operation
   _timer.Change( TIME_INTERVAL_IN_MILLISECONDS, Timeout.Infinite );
}

したがって、同時実行性がないため、ロックメカニズムは必要ありません。タイマーは、次の間隔が経過した後、次のコールバックと長時間実行オペレーションの時間をトリガーします。

タイマーを正確にNミリ秒で実行する必要がある場合は、ストップウォッチを使用して長時間実行オペレーションの時間を測定し、適切にChangeメソッドを呼び出すことをお勧めします。

private void Callback( Object state )
{
   Stopwatch watch = new Stopwatch();

   watch.Start();
   // Long running operation

   _timer.Change( Math.Max( 0, TIME_INTERVAL_IN_MILLISECONDS - watch.ElapsedMilliseconds ), Timeout.Infinite );
}

.NETを使用していて、Jeffrey Richterの本を読んでいないCLR(C#を介したCLR)を使用している人は、できるだけ早く読むことを強くお勧めします。タイマーとスレッドプールについては、そこで詳しく説明されています。


6
同意しないprivate void Callback( Object state ) { // Long running operation _timer.Change( TIME_INTERVAL_IN_MILLISECONDS, Timeout.Infinite ); }Callback操作が完了する前に再度呼び出される場合があります。
Alan Coromano、2012年

2
私が言ったのは、Long running operationそれよりもはるかに時間がかかるかもしれないということでしたTIME_INTERVAL_IN_MILLISECONDS。それから何が起こりますか?
Alan Coromano

31
コールバックが再度呼び出されることはありません。これがポイントです。これが、2番目のパラメーターとしてTimeout.Infiniteを渡す理由です。これは基本的に、タイマーに再度チェックしないことを意味します。次に、操作が完了したら、ティックのスケジュールを変更します。
Ivan Zlatanov 2012年

ここでスレッディングの初心者- ThreadPoolタイマーを渡すと、これでを実行できると思いますか?新しいスレッドが特定の間隔でジョブを実行するために生成され、完了時にスレッドプールに降格されるシナリオを考えています。
jedd.ahyoung 14年

2
System.Threading.Timerは、専用のスレッドではなく、スレッドプールでコールバックを実行するスレッドプールタイマーです。タイマーがコールバックルーチンを完了すると、コールバックを実行したスレッドはプールに戻ります。
Ivan Zlatanov 2014年

14

タイマーを停止する必要はありません。この投稿からの素晴らしい解決策を参照してください

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

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

それは私のケースではありません。タイマーを正確に停止する必要があります。
Alan Coromano

コールバックを何度も入力しないようにしようとしていますか?何を達成しようとしているのですか?
Ivan Leonenko 2012年

1.コールバックに複数回入るのを防ぎます。2.実行回数が多すぎないようにします。
アランCoromano

これはまさにそれがすることです。#2は、オブジェクトがロックされている場合、特にそのような大きな間隔がある場合にifステートメントの直後に返される限り、それほどオーバーヘッドではありません。
Ivan Leonenko 2012年

1
これは、最後の実行後にコードが<interval>以上呼び出されることを保証するものではありません(タイマーの新しいティックが、前のティックがロックを解放してから1マイクロ秒後に発生する可能性があります)。これが厳密な要件であるかどうかによって異なります(問題の説明から完全に明確ではありません)。
Marco Mp

9

使用しています System.Threading.Timer必須ですか?

そうでない場合は、System.Timers.Timer便利なメソッドがStart()ありStop()ます(そして、AutoResetfalseに設定できるプロパティがあるため、これStop()は不要でありStart()、実行後に呼び出すだけです)。


3
はい、しかし、それは実際の要件である可能性があります。または、タイマーが最もよく使用されているため、そのタイマーが選択されたのは偶然です 残念なことに、.NETには大量のタイマーオブジェクトがあり、90%重複していますが、(微妙に)異なっています。もちろん、それが要件である場合、このソリューションはまったく適用されません。
Marco Mp

2
あたりとしてドキュメント:Systems.Timerクラスのみ、.NET Frameworkで提供されています。.NET標準ライブラリには含まれておらず、.NET CoreやユニバーサルWindowsプラットフォーム
NotAgainによると、モニカは2017

3

私はただやります:

private static Timer timer;
 private static void Main()
 {
   timer = new Timer(_ => OnCallBack(), null, 1000 * 10,Timeout.Infinite); //in 10 seconds
   Console.ReadLine();
 }

  private static void OnCallBack()
  {
    timer.Dispose();
    Thread.Sleep(3000); //doing some long operation
    timer = new Timer(_ => OnCallBack(), null, 1000 * 10,Timeout.Infinite); //in 10 seconds
  }

また、自分で周期性を制御しようとしているので、periodパラメータは無視してください。


パラメータを指定0し続けるため、元のコードは可能な限り高速に実行されdueTimeます。からTimer.Change

dueTimeがゼロ(0)の場合、コールバックメソッドがすぐに呼び出されます。


2
タイマーを廃棄する必要はありますか?Change()メソッドを使ってみませんか?
アランコロマノ2012年

21
毎回タイマーを配置することは絶対に不必要で間違っています。
Ivan Zlatanov 2012年

0
 var span = TimeSpan.FromMinutes(2);
 var t = Task.Factory.StartNew(async delegate / () =>
   {
        this.SomeAsync();
        await Task.Delay(span, source.Token);
  }, source.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);

source.Cancel(true/or not);

// or use ThreadPool(whit defaul options thread) like this
Task.Start(()=>{...}), source.Token)

内部でループスレッドを使用したい場合...

public async void RunForestRun(CancellationToken token)
{
  var t = await Task.Factory.StartNew(async delegate
   {
       while (true)
       {
           await Task.Delay(TimeSpan.FromSeconds(1), token)
                 .ContinueWith(task => { Console.WriteLine("End delay"); });
           this.PrintConsole(1);
        }
    }, token) // drop thread options to default values;
}

// And somewhere there
source.Cancel();
//or
token.ThrowIfCancellationRequested(); // try/ catch block requred.
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.