0

さて、週末に奇妙なものを見つけました。バックグラウンド作業を実行するためにいくつかのスレッドを生成する WPF アプリがあります。これらのバックグラウンド スレッドは、作業項目を同期コンテキストに投稿します。これは、1 つのケースを除いてすべて正常に機能しています。スレッドが終了すると、ポップアップ ウィンドウを開くアクションがディスパッチャにポストされることがあります。結局、2 つのスレッドが両方とも Dispatcher にアクションを投稿すると、1 つの処理が開始され、次に Window.ShowDialog(); でポップアップ ウィンドウを開くと、現在の実行パスは一時停止し、ダイアログ ボックスからのフィードバックを待つ必要があります。しかし、ダイアログ ボックスが開くと、Dispatcher がすぐに開始され、ポストされた 2 番目のアクションの実行が開始されるという問題が発生します。これにより、2 つのコード パスが実行されます。

私が話している動作を示すために、いくつかのサンプルコードを投稿しました。2 つのアクションを投稿し、最初のアクションがダイアログ ボックスを開くと、最初のアクションが完了するまで 2 番目のアクションは実行されません。

public partial class Window1 : Window {

    private SynchronizationContext syncContext;
    public Window1() {
        InitializeComponent();
        syncContext = SynchronizationContext.Current;
    }

    private void Button_ClickWithout(object sender, RoutedEventArgs e) {
        // Post an action on the thread pool with the syncContext
        ThreadPool.QueueUserWorkItem(BackgroundCallback, syncContext);
    }

    private void BackgroundCallback(object data) {
        var currentContext = data as SynchronizationContext;

        System.Console.WriteLine("{1}: Thread {0} started", Thread.CurrentThread.ManagedThreadId, currentContext);

        // Simulate work being done
        Thread.Sleep(3000);

        currentContext.Post(UICallback, currentContext);

        System.Console.WriteLine("{1}: Thread {0} finished", Thread.CurrentThread.ManagedThreadId, currentContext);
    }

    private void UICallback(object data) {
        System.Console.WriteLine("{1}: UI Callback started on thread {0}", Thread.CurrentThread.ManagedThreadId, data);

        var popup = new Popup();

        var result = popup.ShowDialog();

        System.Console.WriteLine("{1}: UI Callback finished on thread {0}", Thread.CurrentThread.ManagedThreadId, data);
    }
}

XAML は、Button_ClickWithout OnClick を呼び出すボタンを含む単なる Window です。ボタンを 2 回押して 3 秒間待つと、2 つのダイアログが交互にポップアップすることがわかります。最初のダイアログがポップアップし、閉じられると 2 番目のダイアログがポップアップします。

だから私の質問は: これはバグですか? または、これを軽減して、最初のアクションが Window.ShowDialog() で実行を停止したときに、一度に 1 つのアクションしか処理できないようにするにはどうすればよいですか?

ありがとう、ラウル

4

2 に答える 2

1

私の質問 ( Dispatcher Priority and Binding の使用に関するアドバイス )に対する回答を待っているので、それは pay-it-forward™ になると思いました。

あなたが経験しているのは、ディスパッチャーでのネストされたポンピングです。WPF Threading Modelに関する MSDN の記事、特にページの 3 分の 2 にある「技術的な詳細と問題点」というタイトルのセクションを読むことをお勧めします。便宜上、ネストされたポンピングを説明するサブセクションを以下にコピーします。

ネストされたポンピング

UI スレッドを完全にロックすることができない場合があります。MessageBox クラスの Show メソッドを考えてみましょう。ユーザーが [OK] ボタンをクリックするまで、ショーは戻りません。ただし、対話型にするためにメッセージ ループが必要なウィンドウが作成されます。ユーザーが [OK] をクリックするのを待っている間、元のアプリケーション ウィンドウはユーザー入力に応答しません。ただし、ペイント メッセージの処理は続行します。元のウィンドウは、覆われて表示されると再描画されます。

ここに画像の説明を入力

メッセージボックスウィンドウを担当するスレッドが必要です。WPF はメッセージ ボックス ウィンドウ専用の新しいスレッドを作成できますが、このスレッドは元のウィンドウで無効な要素を描画できません (相互排除に関する前述の説明を思い出してください)。代わりに、WPF はネストされたメッセージ処理システムを使用します。Dispatcher クラスには、PushFrame と呼ばれる特別なメソッドが含まれています。このメソッドは、アプリケーションの現在の実行ポイントを格納し、新しいメッセージ ループを開始します。ネストされたメッセージ ループが終了すると、元の PushFrame 呼び出しの後に実行が再開されます。

この場合、PushFrame は MessageBox.Show の呼び出し時にプログラム コンテキストを維持し、新しいメッセージ ループを開始して、バックグラウンド ウィンドウを再描画し、メッセージ ボックス ウィンドウへの入力を処理します。ユーザーが [OK] をクリックしてポップアップ ウィンドウをクリアすると、ネストされたループが終了し、Show の呼び出し後に制御が再開されます。

于 2011-09-19T20:52:53.820 に答える
0

モーダル ダイアログ ボックスは、オーナー ウィンドウがメッセージを処理することを妨げません。

必要なものを実現するには、UI スレッドに独自のキューを実装する必要があります。おそらく、最初の作業項目が到着したときに「ウェイクアップ」するための同期が必要です。

編集:

また、2 番目のモーダル ダイアログ ボックスが表示されている間に UI スレッドのコール スタックを調べると、スタック内で最初の ShowDialog 呼び出しがその上にあることがわかる場合があります。

編集#2:

独自のキューを実装せずに、これを行う簡単な方法があるかもしれません。の代わりにオブジェクトSynchronizationContextを使用すると、 の優先度でDispatcher呼び出すことができ、適切にキューに入れられます (チェック)。BeginInvokeDispatcherPriority.Normal

于 2010-05-03T17:30:31.510 に答える