C#タイマーは別のスレッドで経過しますか?


97

System.Timers.Timerは、それを作成したスレッドとは別のスレッドで経過しますか?

5秒ごとに起動するタイマーを備えたクラスがあるとします。タイマーが作動すると、elapsedメソッドで、一部のオブジェクトが変更されます。このオブジェクトを変更するのに10秒のように長い時間がかかるとしましょう。このシナリオでスレッドの衝突が発生する可能性はありますか?


これは問題を引き起こす可能性があります。一般に、スレッドプールスレッドは長時間実行されるプロセス用に設計されていないことに注意してください。
グレッグD

私はこれに遭遇し、Windowsサービスでテストしていました。私にとってうまくいったのは、OnTimerイベントの最初の命令としてタイマーを無効にし、タスクを実行し、最後にタイマーを有効にすることでした。これはしばらくの間、本番環境で確実に機能してきました。
スティーブ

回答:


60

以下の場合でSystem.Timers.Timer

以下のブライアン・ギデオンの答えを見てください

以下のためのSystem.Threading.Timer

タイマーに関するMSDNドキュメントは次のように述べています。

System.Threading.Timerクラスは、ThreadPoolスレッドでコールバックを行い、イベントモデルをまったく使用しません。

実際、タイマーは別のスレッドで経過します。


21
確かに、それはまったく別のクラスです。OPはSystem.Timers.Timerクラスについて尋ねました。
ブライアンギデオン

1
ああ、あなたは正しい。msdn.microsoft.com/en-us/library/system.timers.timer.aspxは、「ElapsedイベントがThreadPoolスレッドで発生する」と述べています。そこからも同じ結論だと思います。
ヨレン

6
ええ、そうですが、それほど単純ではありません。私の答えを見てください。
ブライアンギデオン

192

場合によります。にSystem.Timers.Timerは2つの動作モードがあります。

場合SynchronizingObjectに設定されているISynchronizeInvoke場合、その後Elapsedのイベントは、同期オブジェクトをホストするスレッド上で実行されます。通常、これらのISynchronizeInvokeインスタンスは、プレーンな古いものでControlありForm、私たち全員が精通しているインスタンスです。したがって、その場合、ElapsedイベントはUIスレッドで呼び出され、と同様に動作しSystem.Windows.Forms.Timerます。それ以外の場合は、実際ISynchronizeInvokeに使用された特定のインスタンスによって異なります。

場合はSynchronizingObjectnullになり、その後Elapsedのイベントがで呼び出されThreadPoolたスレッドと、それは次のように動作しますSystem.Threading.Timer。実際、実際にはSystem.Threading.Timer背後で使用し、必要に応じてタイマーコールバックを受け取ったでマーシャリング操作を実行ます。


4
タイマーコールバックを新しいスレッドで実行したい場合は、System.Threading.Timerまたはを使用する必要がありますかSystem.Timers.Timer
CJ7 2012年

1
@ cj7:どちらでも可能です。
ブライアンギデオン

そして、もし私が複合型(人)のリストを持っていて、それぞれの人の中で時間を持ちたいなら?これを同じスレッド(すべての人)で実行する必要があります。1人称メソッドを呼び出す場合、2人目は、1人目が経過したイベントを終了するまで待機する必要があるためです。それをしてもいいですか?
Leandro De Mello Fagundes 2014年

4
誰もが... System.Timers.Timer2つの操作モードがあります。ランダムに割り当てられたスレッドプールスレッドで実行することも、ISynchronizeInvokeインスタンスをホストしているスレッドで実行することもできます。それをもっとはっきりさせる方法がわかりません。System.Threading.Timer元の質問とは(もしあれば)ほとんど関係ありません。
ブライアンギデオン

@LeandroDeMelloFagundes使えないのlock
Ozkan

24

前のElapsedがまだ実行されていない限り、各経過イベントは同じスレッドで発生します。

衝突を処理します

これをコンソールに入れてみてください

static void Main(string[] args)
{
    Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);
    var timer = new Timer(1000);
    timer.Elapsed += timer_Elapsed;
    timer.Start();
    Console.ReadLine();
}

static void timer_Elapsed(object sender, ElapsedEventArgs e)
{
    Thread.Sleep(2000);
    Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);
}

あなたはこのようなものを得るでしょう

10
6
12
6
12

ここで、10は呼び出しスレッドで、6と12はbg経過イベントから発生しています。Thread.Sleep(2000);を削除すると、あなたはこのようなものを得るでしょう

10
6
6
6
6

衝突がないので。

しかし、これはまだ問題を抱えています。uが5秒ごとにイベントを発生させ、編集に10秒かかる場合、一部の編集をスキップするにはロックが必要です。


8
timer.Stop()Elapsedイベントメソッドの最初にa を追加し、次にElapsedイベントメソッドtimer.Start()の最後にa を追加すると、Elapsedイベントが衝突しなくなります。
メトロスマーフ

4
timer.Stop()を置く必要はなく、timer.AutoReset = falseを定義するだけです。次に、イベントを処理した後でtimer.Start()を作成します。これは衝突を回避するより良い方法だと思います。
ジョアン・アントゥネス

17

System.Timers.Timerの場合、別のスレッドで、SynchronizingObjectが設定されていない場合。

    static System.Timers.Timer DummyTimer = null;

    static void Main(string[] args)
    {
        try
        {

            Console.WriteLine("Main Thread Id: " + System.Threading.Thread.CurrentThread.ManagedThreadId);

            DummyTimer = new System.Timers.Timer(1000 * 5); // 5 sec interval
            DummyTimer.Enabled = true;
            DummyTimer.Elapsed += new System.Timers.ElapsedEventHandler(OnDummyTimerFired);
            DummyTimer.AutoReset = true;

            DummyTimer.Start();

            Console.WriteLine("Hit any key to exit");
            Console.ReadLine();
        }
        catch (Exception Ex)
        {
            Console.WriteLine(Ex.Message);
        }

        return;
    }

    static void OnDummyTimerFired(object Sender, System.Timers.ElapsedEventArgs e)
    {
        Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId);
        return;
    }

DummyTimerが5秒間隔で起動した場合に表示される出力:

Main Thread Id: 9
   12
   12
   12
   12
   12
   ... 

したがって、ご覧のとおり、OnDummyTimerFiredはワーカースレッドで実行されます。

いいえ、さらに複雑です-間隔を10ミリ秒に短縮すると、

Main Thread Id: 9
   11
   13
   12
   22
   17
   ... 

これは、次のティックが発生したときにOnDummyTimerFiredの前の実行が行われない場合、.NETがこのジョブを実行するための新しいスレッドを作成するためです。

さらに複雑なことに、「System.Timers.Timerクラスは、このジレンマに対処する簡単な方法を提供します。これは、パブリックSynchronizingObjectプロパティを公開します。このプロパティをWindowsフォーム(またはWindowsフォーム上のコントロール)のインスタンスに設定すると、 Elapsedイベントハンドラーのコードが、SynchronizingObjectがインスタンス化されたのと同じスレッドで実行されることを確認します。」

http://msdn.microsoft.com/en-us/magazine/cc164015.aspx#S2


それは良い例です。今まで知らなかったので、あちこちにいくつかのロックを設定する必要があります。完璧です。
Olaru Mircea

また、タイマーがコンソールアプリケーションのメインスレッドでElapsedイベントを発生させたい場合はどうすればよいでしょうか。
ジャックトリック

13

経過したイベントが間隔より長くかかる場合、経過したイベントを発生させる別のスレッドを作成します。しかし、これには回避策があります

static void timer_Elapsed(object sender, ElapsedEventArgs e)    
{     
   try
   {
      timer.Stop(); 
      Thread.Sleep(2000);        
      Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);    
   }
   finally
   {
     timer.Start();
   }
}

+1シンプルで明確な答えですが、これは常に機能するとは限りません。むしろ、タイマーのロックまたはSynchronizingObjectプロパティを使用する必要があります。
RollerCosta
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.