-1

注意:私はUIコードを制御していません。class PeriodicalThingコードのみ。


このシナリオを考えてみましょう。

UIスレッドはUIをうまく実行していますが、定期的にイベントを発生させる別のバックグラウンドスレッドがあります。イベントは一部のUIによってサブスクライブされます。つまり、イベントハンドラーは通常を使用する必要がありますInvoke

私が必要としているのは、クリーンアップ操作(クリーンアップとは、基本的にバックグラウンドスレッドを停止することを意味します)を安全な方法で実行する方法です。これにより、次のことが保証されます。

  1. ユーザー定義コード(つまり、イベントハンドラーコード)は、実行中に非同期的に中止されません
  2. クリーンアップ関数が戻った後に実行または実行される定期的な操作はもうありません
  3. クリーンアップ関数が戻った後、これ以上イベントハンドラーが実行または実行されることはありません

何か思いついたのですが、行き詰まりがあります。デッドロックは基本的にユーザー定義コードのエラーであり、を使用するBeginInvokeことで問題を修正できた可能性がありますが、重要なプログラミングエラーの場合の完全なデッドロックは解決策ではありません。
また、フォームの呼び出しリストは;の後にたまたまクリアされるためBeginInvoke、シナリオでのみ機能することに注意してください。一貫しているように見えますが、まだ文書化されていません。FormClosingFormClosing

解決策がある場合、それは明らかに明らかではありませんが、おそらく私はトリックを逃しています。これまでに同じような問題に遭遇した人は誰もいないとは信じられません。

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スレッドから移動する方法を知っているかもしれませんが、それをどのように行うかはわかりません。

4

3 に答える 3

1

問題はここにあります

public void CleanUp()
{
    this.abort = true;
    // ui thread will deadlock here
    this.PeriodicalThread.Join();  // just delete this
}

Join()は呼び出し元のスレッドをブロック(!)します。そして、それは順番にすべての呼び出しアクションをブロックします。

このコインの他の部分は

{
  if (this.InvokeRequired) // actually always true
    {
        // periodical thread will deadlock here
    //    this.Invoke(
        this.BeginInvoke(            // doesn't wait so it doesn't block
            new Action<object, EventArgs>(this.ThingsHappenedHandler), sender, e)
            );
        return;
    }
    this.listBox1.Items.Add("things happened");
 }

voidBeginInvokeは、MessageLoopに戻ってオーバーロードしない限り、とにかくInvokeを改善したものです。

ただし、Join()を削除してください。役に立たない。

一部のパーツは管理下にないため、編集してください。

以下は確かに答えであり、それは可能です:

Thread.Join()でUIスレッドを中断し、待機中のすべての呼び出しを実行させてから、Thread.Join()に戻ります。

public void CleanUp()
{
    this.abort = true;
 
    while (! this.PeriodicalThread.Join(20)) 
    { 
       Application.DoEvents(); 
    }
}

これはデッドロックにはなりませんが、の使用には問題がありますApplication.DoEvents()。他のすべてのイベント(FormClosing)で何が起こるかを確認する必要があり、それも制御できません...
おそらく機能しますが、いくつかの厳密なテストが必要です。


スレッドとイベントを混合しているので、次のパターンを使用します。

protected virtual void OnThingsHappaned(EventArgs e)
{
    var handler = ThingsHappened;

    if (handler  != null)
    {
        handler (this, e);
    }
}
于 2012-09-19T20:23:16.450 に答える
0

書き直しを提案するのは好きではありませんが、?を使用することを提案できBackgroundWorkerますか?

私はそれを迅速で簡単な非同期プロセスだと思っており、そのステータス(パーセンテージやステータスオブジェクトなど)について呼び出し元のスレッドにメッセージを送り返すことができます。

簡単なハウツーはここにあります:http: //msdn.microsoft.com/en-us/library/cc221403 (v = vs.95).aspx

于 2012-09-19T20:36:31.327 に答える
0

これがあなたのケースに当てはまるかどうかはわかりませんが、私はBackGroundWorkerとSupportCancellationを使用しています。オフの場合はコメントしてください。削除します。

BackgroundWorker.WorkerSupportsCancellationプロパティ

そして、その場で作成される高価なFlowDocumentを遅延ロードするために、これを使用してハンマーで叩きます。次のドキュメントをクリックした場合は、その作業をキャンセルして次のドキュメントから開始する必要があります。

申し訳ありませんが、他のBackGroundWorkerと、使用できないというコメントを見ました。UIレベルでは使用していません。WinFormsも使用していません。私はビジネス/データレイヤーで使用しています。

于 2012-09-19T20:44:24.770 に答える