タイマーをC#コンソールアプリケーションに追加する方法


132

これだけ-C#コンソールアプリケーションにタイマーをどのように追加しますか?コーディング例を提供できればすばらしいと思います。


16
注意:ここでの回答にはバグがあり、Timerオブジェクトはガベージコレクションを取得します。タイマーへの参照を静的変数に格納して、タイマーが作動し続けるようにする必要があります。
ハンスパッサント2014

@HansPassant私の回答の明確なステートメントを見逃しているようです:「Windowsサービスを開発していて、タイマーを定期的に実行する必要がある場合は、常に静的(VB.NETで共有)System.Threading.Timerを使用することをお勧めします。これにより、タイマーオブジェクトの時期尚早のガベージコレクションが回避されます。」人々がランダムな例をコピーして盲目的に使用したい場合、それは彼らの問題です。
Ash

回答:


120

これは非常に便利ですが、時間が経つにつれシミュレーションを行うには、少し時間がかかるコマンドを実行する必要があります。これは2番目の例では非常に明確です。

ただし、forループを使用して一部の機能を実行するスタイルでは、多くのデバイスリソースが永久に使用されます。代わりに、ガベージコレクターを使用してそのようなことを行うことができます。

この変更は、同じ本のCLR Via C#Third Edのコードで確認できます。

using System;
using System.Threading;

public static class Program {

   public static void Main() {
      // Create a Timer object that knows to call our TimerCallback
      // method once every 2000 milliseconds.
      Timer t = new Timer(TimerCallback, null, 0, 2000);
      // Wait for the user to hit <Enter>
      Console.ReadLine();
   }

   private static void TimerCallback(Object o) {
      // Display the date/time when this method got called.
      Console.WriteLine("In TimerCallback: " + DateTime.Now);
      // Force a garbage collection to occur for this demo.
      GC.Collect();
   }
}

3
Khalid、これは非常に役に立ちました。ありがとう。console.readline()とGC.Collectは、まさに私が必要としていたものです。
Seth Spearman

9
@ラルフ・ウィルゴス、なぜGC.Collect(); 必要とされている?
Puchacz 2015

2
@Puchacz呼び出す意味がわかりませんGC.Collect()。収集するものはありません。GC.KeepAlive(t)後に呼び出された場合、それは理にかなっていますConsole.ReadLine();
newprint '20 / 10/20

1
最初のコールバック後に終了しました
BadPiggie

1
@Khalid Al Hajami「ただし、forループを使用して一部の機能を実行するスタイルでは、多くのデバイスリソースが永久に使用されます。代わりに、ガベージコレクターを使用してそのようなことを行うことができます。」これはまったく無意味なゴミです。ガベージコレクタはまったく無関係です。これを本からコピーして、何をコピーしていたのか分からなかったのですか?
Ash

65

System.Threading.Timerクラスを使用します。

System.Windows.Forms.Timerは、主に単一のスレッド、通常はWindowsフォームUIスレッドで使用するために設計されています。

.NETフレームワークの開発の早い段階で追加されたSystem.Timersクラスもあります。ただし、これはSystem.Threading.Timerのラッパーにすぎないため、通常は代わりにSystem.Threading.Timerクラスを使用することをお勧めします。

また、Windowsサービスを開発していて、タイマーを定期的に実行する必要がある場合は、常に静的(VB.NETで共有)のSystem.Threading.Timerを使用することをお勧めします。これにより、タイマーオブジェクトの時期尚早のガベージコレクションが回避されます。

コンソールアプリケーションのタイマーの例を次に示します。

using System; 
using System.Threading; 
public static class Program 
{ 
    public static void Main() 
    { 
       Console.WriteLine("Main thread: starting a timer"); 
       Timer t = new Timer(ComputeBoundOp, 5, 0, 2000); 
       Console.WriteLine("Main thread: Doing other work here...");
       Thread.Sleep(10000); // Simulating other work (10 seconds)
       t.Dispose(); // Cancel the timer now
    }
    // This method's signature must match the TimerCallback delegate
    private static void ComputeBoundOp(Object state) 
    { 
       // This method is executed by a thread pool thread 
       Console.WriteLine("In ComputeBoundOp: state={0}", state); 
       Thread.Sleep(1000); // Simulates other work (1 second)
       // When this method returns, the thread goes back 
       // to the pool and waits for another task 
    }
}

ジェフ・リヒター著の本CLR Via C#から。ちなみに、この本では、第23章の3種類のタイマーの根拠について説明しています。


実際のコーディングについてもう少し情報を提供できますか?
Johan Bresler、2008年


エリック、私は試したことはありませんが、問題があったとしても珍しくはありません。また、ある種のスレッド間同期を実行しようとしていることにも気づきました。これは、常に正しく理解するのが難しい領域です。設計でそれを回避できる場合は、常にそれを行うのが賢明です。
Ash

1
Ash-msdnの例については間違いなく同意します。同期コードをすぐに割引するつもりはありませんが、ティマーが独自のスレッドで実行される場合は、マルチスレッドアプリを作成していて、同期に関する問題に注意する必要があります。
Eric Tuttleman、2008年

1
TimerCallbackデリゲートシグネチャに一致するメソッドが複数ある場合はどうなりますか?
Ozkan

22

簡単な1秒のタイマー刻みを作成するコードは次のとおりです。

  using System;
  using System.Threading;

  class TimerExample
  {
      static public void Tick(Object stateInfo)
      {
          Console.WriteLine("Tick: {0}", DateTime.Now.ToString("h:mm:ss"));
      }

      static void Main()
      {
          TimerCallback callback = new TimerCallback(Tick);

          Console.WriteLine("Creating timer: {0}\n", 
                             DateTime.Now.ToString("h:mm:ss"));

          // create a one second timer tick
          Timer stateTimer = new Timer(callback, null, 0, 1000);

          // loop here forever
          for (; ; )
          {
              // add a sleep for 100 mSec to reduce CPU usage
              Thread.Sleep(100);
          }
      }
  }

そして、ここに結果の出力があります:

    c:\temp>timer.exe
    Creating timer: 5:22:40

    Tick: 5:22:40
    Tick: 5:22:41
    Tick: 5:22:42
    Tick: 5:22:43
    Tick: 5:22:44
    Tick: 5:22:45
    Tick: 5:22:46
    Tick: 5:22:47

編集:ハードスピンループをコードに追加することは、CPUサイクルを消費して利益を得られないため、決して良い考えではありません。この場合、アプリケーションの終了を停止するためだけにループが追加され、スレッドのアクションを監視できるようになりました。しかし、正確さのためとCPU使用率を減らすために、単純なスリープ呼び出しがそのループに追加されました。


7
for(;;){}は、CPU使用率を100%にします。
Seth Spearman

1
無限のforループがある場合、CPUが100%になることは明らかではありませんか。これを修正するには、ループにスリープ呼び出しを追加するだけです。
2013

3
forループをwhileループにする必要があるかどうか、およびCPUが100%になる理由に何人がこだわっているのかは驚くべきことです。木を見逃すことについて話してください!方位角、私は、while(1)が無限ループとどのように異なるかを個人的に知りたいですか?確かに、CLRコンパイラオプティマイザを作成する人々は、これら2つのコード構造がまったく同じCLRコードを作成することを確認しますか?
Blake7、2014

1
while(1)が機能しない理由の1つは、有効ではないことです。c#:test.cs(21,20):エラーCS0031:定数値 '1'を 'bool'に変換できません
Blake7

1
私のマシン(win8.1、i5)ではなく、約20〜30%しかありませんでした。当時、どのようなコンピューターを使用していましたか?@SethSpearman
慎三

17

少し楽しみましょう

using System;
using System.Timers;

namespace TimerExample
{
    class Program
    {
        static Timer timer = new Timer(1000);
        static int i = 10;

        static void Main(string[] args)
        {            
            timer.Elapsed+=timer_Elapsed;
            timer.Start(); Console.Read();
        }

        private static void timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            i--;

            Console.Clear();
            Console.WriteLine("=================================================");
            Console.WriteLine("                  DEFUSE THE BOMB");
            Console.WriteLine(""); 
            Console.WriteLine("                Time Remaining:  " + i.ToString());
            Console.WriteLine("");        
            Console.WriteLine("=================================================");

            if (i == 0) 
            {
                Console.Clear();
                Console.WriteLine("");
                Console.WriteLine("==============================================");
                Console.WriteLine("         B O O O O O M M M M M ! ! ! !");
                Console.WriteLine("");
                Console.WriteLine("               G A M E  O V E R");
                Console.WriteLine("==============================================");

                timer.Close();
                timer.Dispose();
            }

            GC.Collect();
        }
    }
}

11

またはRxを使用して、短くて甘い:

static void Main()
{
Observable.Interval(TimeSpan.FromSeconds(10)).Subscribe(t => Console.WriteLine("I am called... {0}", t));

for (; ; ) { }
}

1
本当に最高のソリューションです!
ドミトリーLedentsov 2013

8
非常に判読不能で、ベストプラクティスに反しています。見た目は素晴らしいですが、一部のPPLがwtfやpooを使用するため、本番環境では使用しないでください。
Piotr Kula 2013年

2
Reactive Extensions(Rx)は、2年間積極的に開発されていません。さらに、例にはコンテキストがなく、混乱します。図やフローの例についてはほとんど知りません。
James Bailey、

4

もう少し細かい制御が必要な場合は独自のタイミングメカニズムを使用することもできますが、精度とコード/複雑度が低下する可能性がありますが、タイマーの使用をお勧めします。ただし、実際のタイミングスレッドを制御する必要がある場合は、これを使用します。

private void ThreadLoop(object callback)
{
    while(true)
    {
        ((Delegate) callback).DynamicInvoke(null);
        Thread.Sleep(5000);
    }
}

タイミングスレッドになります(これを変更して、必要なときに停止し、必要な時間間隔で停止します)。

使用/開始するには、次のようにします。

Thread t = new Thread(new ParameterizedThreadStart(ThreadLoop));

t.Start((Action)CallBack);

コールバックは、間隔ごとに呼び出す必要のあるパラメーターのないvoidメソッドです。例えば:

private void CallBack()
{
    //Do Something.
}

1
タイムアウトになるまでバッチジョブを実行したい場合、ここでの提案は最適でしょうか?
Johan Bresler、2008年

1

自分で作成することもできます(利用可能なオプションに不満がある場合)。

自分で作成する Timer実装をことは、かなり基本的なことです。

これは、コードベースの他の部分と同じスレッドでCOMオブジェクトにアクセスする必要があるアプリケーションの例です。

/// <summary>
/// Internal timer for window.setTimeout() and window.setInterval().
/// This is to ensure that async calls always run on the same thread.
/// </summary>
public class Timer : IDisposable {

    public void Tick()
    {
        if (Enabled && Environment.TickCount >= nextTick)
        {
            Callback.Invoke(this, null);
            nextTick = Environment.TickCount + Interval;
        }
    }

    private int nextTick = 0;

    public void Start()
    {
        this.Enabled = true;
        Interval = interval;
    }

    public void Stop()
    {
        this.Enabled = false;
    }

    public event EventHandler Callback;

    public bool Enabled = false;

    private int interval = 1000;

    public int Interval
    {
        get { return interval; }
        set { interval = value; nextTick = Environment.TickCount + interval; }
    }

    public void Dispose()
    {
        this.Callback = null;
        this.Stop();
    }

}

次のようにイベントを追加できます。

Timer timer = new Timer();
timer.Callback += delegate
{
    if (once) { timer.Enabled = false; }
    Callback.execute(callbackId, args);
};
timer.Enabled = true;
timer.Interval = ms;
timer.Start();
Window.timers.Add(Environment.TickCount, timer);

タイマーが機能することを確認するには、次のようにエンドレスループを作成する必要があります。

while (true) {
     // Create a new list in case a new timer
     // is added/removed during a callback.
     foreach (Timer timer in new List<Timer>(timers.Values))
     {
         timer.Tick();
     }
}

1

C#5.0以降および.NET Framework 4.5以降では、async / awaitを使用できます。

async void RunMethodEvery(Action method, double seconds)
{
    while (true)
    {
        await Task.Delay(TimeSpan.FromSeconds(seconds));
        method();
    }
 }

0

文書

そこにあります:)

public static void Main()
   {
      SetTimer();

      Console.WriteLine("\nPress the Enter key to exit the application...\n");
      Console.WriteLine("The application started at {0:HH:mm:ss.fff}", DateTime.Now);
      Console.ReadLine();
      aTimer.Stop();
      aTimer.Dispose();

      Console.WriteLine("Terminating the application...");
   }

   private static void SetTimer()
   {
        // Create a timer with a two second interval.
        aTimer = new System.Timers.Timer(2000);
        // Hook up the Elapsed event for the timer. 
        aTimer.Elapsed += OnTimedEvent;
        aTimer.AutoReset = true;
        aTimer.Enabled = true;
    }

    private static void OnTimedEvent(Object source, ElapsedEventArgs e)
    {
        Console.WriteLine("The Elapsed event was raised at {0:HH:mm:ss.fff}",
                          e.SignalTime);
    }

0

Microsoftのガイドラインに従うことをお勧めします( https://docs.microsoft.com/en-us/dotnet/api/system.timers.timer.interval?view=netcore-3.1)。

私が最初に使用してみましたSystem.Threading;

var myTimer = new Timer((e) =>
{
   // Code
}, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));

しかし、それは約20分後に継続的に停止しました。

それで、ソリューション設定を試しました

GC.KeepAlive(myTimer)

または

for (; ; ) { }
}

しかし、私の場合はうまくいきませんでした。

Microsoftのドキュメントに従って、それは完全に機能しました:

using System;
using System.Timers;

public class Example
{
    private static Timer aTimer;

    public static void Main()
    {
        // Create a timer and set a two second interval.
        aTimer = new System.Timers.Timer();
        aTimer.Interval = 2000;

        // Hook up the Elapsed event for the timer. 
        aTimer.Elapsed += OnTimedEvent;

        // Have the timer fire repeated events (true is the default)
        aTimer.AutoReset = true;

        // Start the timer
        aTimer.Enabled = true;

        Console.WriteLine("Press the Enter key to exit the program at any time... ");
        Console.ReadLine();
    }

    private static void OnTimedEvent(Object source, System.Timers.ElapsedEventArgs e)
    {
        Console.WriteLine("The Elapsed event was raised at {0}", e.SignalTime);
    }
}
// The example displays output like the following: 
//       Press the Enter key to exit the program at any time... 
//       The Elapsed event was raised at 5/20/2015 8:48:58 PM 
//       The Elapsed event was raised at 5/20/2015 8:49:00 PM 
//       The Elapsed event was raised at 5/20/2015 8:49:02 PM 
//       The Elapsed event was raised at 5/20/2015 8:49:04 PM 
//       The Elapsed event was raised at 5/20/2015 8:49:06 PM 
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.