.Netのタイマー

最近、.NETの標準タイマーの1つであるSystem.Threading.Timerがどのように機能するかを開発者が完全に理解していないという事実に遭遇したのはこれが初めてではありません。

つまり 一般に、彼らはタイマーが何かをすることを理解しているようで、おそらくThreadPoolで-定期的に何かを実行するためにそれを使用する場合、それは非常に適しています。 しかし、複数のタイマーを作成して1000を置く必要がある場合、人々は心配し始めます:何かがそこに間違っているとしたらどうでしょうか。



この「神秘的な」System.Threading.Timerに光を当てたいと思います。



.NETには他のタイマーも存在しますが、主に特定の問題を解決するために設計されています(たとえば、GUIアプリケーションを作成するため)。 これは、「システム」の問題を解決したり、ライブラリで使用したりすることを目的としています。



タイマーを実装する方法について少し説明します。



たとえば、定期的に実行される作業単位ごとに、特定の時間間隔で起動して作業を実行し、再びスリープ状態になる個別のスレッドを作成できます。 これが最良の選択肢ではないことは明らかです。



他の方法でカーネルオブジェクト「タイマー」を使用することもできます。 定期的な作業単位ごとに、カーネルオブジェクトを作成し、個別のスレッドで次のスタイルでそれらを期待します。



WaitHandle.WaitAny(/*timerHandles[]*/)
      
      





ただし、残念ながら、.NETには、そのようなオブジェクト(カーネルタイマー)を直接操作するためのAPIはありません。



3番目のタイマー実装オプションがあります(System.Threading.Timerクラスの開発者から入手)

P / Invokeメカニズムを使用してアプリケーションドメインに最初のタイマーを作成すると、「タイマー」カーネルオブジェクトが作成されます。これはSystem.Threading.TimerQueueクラスで確認できます。



  [SecurityCritical] [SuppressUnmanagedCodeSecurity] [DllImport("QCall", CharSet = CharSet.Unicode)] private static TimerQueue.AppDomainTimerSafeHandle CreateAppDomainTimer(uint dueTime); // some code if (this.m_appDomainTimer == null || this.m_appDomainTimer.IsInvalid) { this.m_appDomainTimer = TimerQueue.CreateAppDomainTimer(dueTime); // some code
      
      







また、次のタイマーが作動するまで待機する必要がある量を計算し、カーネルオブジェクトの「タイマー」を設定して待機する別のスレッドを作成します。

それがどのように見えるか見てみましょう。 コンソールプロジェクトを作成し、SOSデバッグ拡張機能を有効にします。



画像



ご覧のとおり、タイマーを作成する前に、「メイン」スレッドと「ファイナライザー」スレッドの2つのスレッドしかありません。 1行下に行きましょう。



画像



2つのスレッドがあります。1つはID 3、これはまさにカーネルオブジェクト「タイマー」で動作するスレッドです。 2番目のID 4はプールワークフローです。まだ開始する時間がなく、コールバックを実行します。



複数のタイマーを順番に作成する場合、どのように機能しますか

System.Threading.TimerQueueクラスに戻ります。 彼はシングルトンです。 次の形式のコードを記述するたびに:

  new Timer(First, null, 0, 250);
      
      





これにより、System.Threading.TimerQueueTimerクラスのインスタンスが内部キュー(LinkedListのようなもの)に追加されます。 つまり このクラスには、作成されたすべてのタイマーが含まれます(ドメイン内にあると思います)。

最初のタイマーが作成された後。 TimerQueueは定期的にFireNextTimersメソッドを呼び出します。

彼は何をしますか(コードは長く、ソースコードを引用しませんでした。興味のある人は誰でも見ることができます):

格納されているすべてのタイマーをすばやく実行し、タイマーが次にトリガーされるまでの時間を見つけ、この間隔で通知を送信するようにタイマーカーネルオブジェクトを設定します。 この通知が受信されるとすぐに、次の操作の時間が再カウントされ、タイマーオブジェクトが新しい間隔に設定されます。 新しいタイマーを追加すると、次の通知の時間が再カウントされます。



1000個のタイマーを作成して、何が起こるか見てみましょう。



画像



1000個のタイマーを作成しても、1000個のスレッドを作成する必要はありません。 CLRは、カーネルタイマーを操作するための1つのスレッドと、タイマー操作を処理するための複数のワーカースレッドを作成しました。



合計:

System.Threading.Timerクラスを使用する場合、1つの(アプリケーションドメインごとに)カーネルオブジェクト「timer」と、「heap」データ構造の動作と同様の原理で動作する1つのスレッドを作成します。

1000個のタイマーの問題について-アプリケーションでこのような数のタイマーを作成するのは採算が取れていないのか、特定のケースを個別に検討する必要があると思います。 しかし、タイマーが内部にどのように配置されているかを知ることは、正しい決定を下すのに役立ちます。



Windows 7 64、.Net 4.5、VS2012でテスト済み。

参考資料:ダフィー「Windowsでの同時プログラミング」、MSDN



All Articles