スレッドをカプセル化するタイムラインというオブジェクトがあります。イベントはタイムラインでスケジュールできます。スレッドは、イベントを実行する時間になるまで待機し、それを実行し、スリープに戻ります ((a) 次のイベントに到達するまでの時間、または (b) イベントがなくなった場合は無期限に)。
スリープは WaitEventHandle で処理されます。これは、イベントのリストが変更されたとき (スリープ遅延を調整する必要があるため)、またはスレッドを停止する必要があるとき (スレッドが正常に終了できるようにするため) にトリガーされます。
デストラクタは Stop() を呼び出します。IDisposable も実装し、Dispose() も Stop() を呼び出します。
それでも、このコンポーネントをフォーム アプリケーションで使用すると、フォームを閉じるときにアプリケーションが正しくシャットダウンされません。何らかの理由で Stop() が呼び出されないため、.NET がすべてのスレッドの終了を待機することを決定する前に、オブジェクトのデストラクタがトリガーされず、Dispose() メソッドが呼び出されません。
解決策は、FormClose イベントで明示的に Dispose() を自分で呼び出すことだと思いますが、このクラスはライブラリにあり、実際にはより深いレイヤーであるため (つまり、アプリケーション開発者はタイムラインを実際に見ることはありません)クラス)、これは非常に見苦しく、アプリケーション開発者にとって余分な (不必要な) 落とし穴のようです。リソースの解放が問題になったときに通常使用する using() 句は、これが長寿命のオブジェクトになるため適用されません。
一方では、.NET がガベージ コレクションの最終ラウンドを実行する前に、すべてのスレッドが終了するのを待ちたいと思うことは理解できますが、この場合、非常に厄介な状況が生じます。
ライブラリのコンシューマーに要件を追加せずに、スレッド自体を適切にクリーンアップするにはどうすればよいですか? 別の言い方をすれば、アプリケーションが終了するときに、すべてのスレッドが終了するのを待つ前に、.NET がオブジェクトに通知するようにするにはどうすればよいでしょうか?
編集:クライアントプログラムがスレッドを認識しても問題ないと言っている人々に応えて:私は丁重に反対します。
元の投稿で述べたように、スレッドは別のオブジェクト (アニメーター) に隠されています。別のオブジェクトのアニメーターをインスタンス化し、「このライトを 800 ミリ秒点滅させる」などのアニメーションを実行するように指示します。
Animator オブジェクトの消費者として、Animator がライトが正確に 800 ミリ秒点滅することを確認する方法は気にしません。スレッドを開始しますか?私は気にしない。非表示のウィンドウを作成し、システム タイマー (ew) を使用しますか? 私は気にしない。明かりを点けたり消したりするために小人を雇いますか?私は気にしない。
そして、アニメーターを作成した場合、他のすべてのオブジェクトとは対照的に、プログラムの終了時にそれを追跡し、特別なメソッドを呼び出す必要があることを特に気にする必要はありません。ライブラリの消費者ではなく、ライブラリの実装者が関心を持つべきです。
編集:コードは実際には表示するのに十分短いです。リストにイベントを追加するメソッドはありません。参照用に含めます。
internal class Timeline : IDisposable {
private Thread eventThread;
private volatile bool active;
private SortedList<DateTime, MethodInvoker> events = new SortedList<DateTime,MethodInvoker>();
private EventWaitHandle wakeup = new EventWaitHandle(false, EventResetMode.AutoReset);
internal Timeline() {
active = true;
eventThread = new Thread(executeEvents);
eventThread.Start();
}
~Timeline() {
Dispose();
}
private DateTime NextEvent {
get {
lock(events)
return events.Keys[0];
}
}
private void executeEvents() {
while (active) {
// Process all events that are due
while (events.Count > 0 && NextEvent <= DateTime.Now) {
lock(events) {
events.Values[0]();
events.RemoveAt(0);
}
}
// Wait for the next event, or until one is scheduled
if (events.Count > 0)
wakeup.WaitOne((int)(NextEvent - DateTime.Now).TotalMilliseconds);
else
wakeup.WaitOne();
}
}
internal void Stop() {
active = false;
wakeup.Set();
}
public void Dispose() {
Stop();
}
}