注意:私はUIコードを制御していません。class PeriodicalThing
コードのみ。
このシナリオを考えてみましょう。
UIスレッドはUIをうまく実行していますが、定期的にイベントを発生させる別のバックグラウンドスレッドがあります。イベントは一部のUIによってサブスクライブされます。つまり、イベントハンドラーは通常を使用する必要がありますInvoke
。
私が必要としているのは、クリーンアップ操作(クリーンアップとは、基本的にバックグラウンドスレッドを停止することを意味します)を安全な方法で実行する方法です。これにより、次のことが保証されます。
- ユーザー定義コード(つまり、イベントハンドラーコード)は、実行中に非同期的に中止されません
- クリーンアップ関数が戻った後に実行または実行される定期的な操作はもうありません
- クリーンアップ関数が戻った後、これ以上イベントハンドラーが実行または実行されることはありません
何か思いついたのですが、行き詰まりがあります。デッドロックは基本的にユーザー定義コードのエラーであり、を使用するBeginInvoke
ことで問題を修正できた可能性がありますが、重要なプログラミングエラーの場合の完全なデッドロックは解決策ではありません。
また、フォームの呼び出しリストは;の後にたまたまクリアされるためBeginInvoke
、シナリオでのみ機能することに注意してください。一貫しているように見えますが、まだ文書化されていません。FormClosing
FormClosing
解決策がある場合、それは明らかに明らかではありませんが、おそらく私はトリックを逃しています。これまでに同じような問題に遭遇した人は誰もいないとは信じられません。
class PeriodicalThing
{
bool abort = false;
Thread PeriodicalThread;
...
PeriodicalThreadProc()
{
while (!this.abort)
{
DoStuff();
OnThingsHappened(new EventArgs(...));
}
}
public event EventHandler<EventArgs> ThingsHappened;
protected virtual void OnThingsHappaned(EventArgs e)
{
// update -- oversight by me - see Henk Holterman's answer
var handler = this.ThingsHappened;
if (handler != null)
{
handler(this, e);
}
}
public void CleanUp()
{
this.abort = true;
// ui thread will deadlock here
this.PeriodicalThread.Join();
}
}
...
// user-defined code; consider this immutable
class Form1 : Form
{
.ctor()
{
...
this.PeriodicalThing.ThingsHappened += this.ThingsHappenedHandler
}
private void ThingsHappenedHandler(object sender, EventArgs e)
{
if (this.InvokeRequired) // actually always true
{
// periodical thread will deadlock here
this.Invoke(
new Action<object, EventArgs>(this.ThingsHappenedHandler), sender, e)
);
return;
}
this.listBox1.Items.Add("things happened");
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
this.PeriodicalThing.CleanUp();
}
}
UIスレッドで発生したなどのイベントが原因でUIスレッドがシャットダウンしている場合FormClosing
、クリーンアップがトリガーされます。Invoke
この時点でバックグラウンドスレッドがを発行するとInvoke
、UIスレッドが現在のイベントハンドラー(最初にクリーンアップをトリガーした)で終了するまでブロックする必要があります。また、クリーンアップ操作はバックグラウンドスレッド(したがって現在のInvoke)が終了するのを待つ必要があり、デッドロックが発生します。
最適な解決策は、でUIスレッドを中断しThread.Join()
、待機中のすべての呼び出しを実行させてから、に戻ることThread.Join()
です。しかし、C#ではそれは不可能に思えます。誰かが、ヘルパースレッドを使用してクリーンアップメソッドをUIスレッドから移動する方法を知っているかもしれませんが、それをどのように行うかはわかりません。