3

スレッドをカプセル化するタイムラインというオブジェクトがあります。イベントはタイムラインでスケジュールできます。スレッドは、イベントを実行する時間になるまで待機し、それを実行し、スリープに戻ります ((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();
    }
}
4

5 に答える 5

4

Thread.IsBackgroundプロパティを true に設定してみてはいかがでしょうか。

eventThread = new Thread(executeEvents);
eventThread.IsBackground = true;
eventThread.Start();

別のオプションは、割り込みメソッドを使用してウェイクアップすることです。中断しているスレッドでをキャッチしThreadInterruptedException、それが発生したときにシャットダウンすることを確認してください。

active = false;
eventThread.Interrupt();
try { eventThread.Join(); }   // Wait for graceful shutdown
catch (Exception) { }

あなたのそれがどのように機能するかはよくわかりませんEventWaitHandle...一度似たようなことをしたとき、私はちょうど通常のThread.Sleep=)を使用しました

于 2009-02-17T21:55:10.870 に答える
3

クライアントがシャットダウンのためにスレッドを Stop() することを要求することはまったく不合理だとは思いません。実行を継続してもアプリケーションの終了を停止しないスレッドを作成する方法はいくつかあります (ただし、頭の中で詳細を把握していません)。しかし、ワーカー スレッドの起動と終了を期待することは、クライアントにとってそれほど大きな負担ではありません。

于 2009-02-17T21:50:30.397 に答える
1

Application::ApplicationExitは静的イベントです。それをリッスンして特別なクリーンアップ作業を行うことは許容されますか?

IDisposable を実装することは、クライアントが "using" ブロックでクラスを使用する必要があることを十分に示しているはずです。

于 2009-02-17T22:27:30.807 に答える
1

クライアントの協力なしに .NET にスレッドを通知させる方法はありません。長時間実行されるバックグラウンド スレッドを持つようにライブラリを設計している場合、クライアント アプリはそれを認識するように設計する必要があります。

于 2009-02-17T21:53:37.160 に答える
0

Dispose(true)を呼び出すファイナライザーの実装を含め、IDisposableを適切に実装します。その後、Animatorオブジェクトは、必要に応じてスレッドを停止するなど、必要なクリーンアップを実行できます。

于 2009-02-17T23:33:23.567 に答える