2010-05-03 84 views
0

好的,所以我在週末發現了一些奇怪的東西。我有一個WPF應用程序,它產生了一些線程來執行後臺工作。這些後臺線程然後將工作項目發佈到我的同步上下文中。這一切都工作正常,除了一個案件。當我的線程完成時,他們會發佈一個動作到調度器上,這個動作將打開一個彈出窗口。最終發生的事情是,如果兩個線程都在Dispatcher上發佈一個動作,它就開始處理一個動作,然後如果我用Window.ShowDialog()打開一個Popup窗口;當前執行路徑暫停等待對話框中的反饋。但問題在於,當對話框打開時,分派器隨即啓動並立即開始運行已發佈的第二個操作。這導致兩個代碼路徑被執行。第一個消息框保持打開狀態,而第二個消息框正在運行,因爲我的應用程序狀態是未知的,因爲第一個操作從未完成。WPF調度程序執行多個執行路徑

我已經發布了一些示例代碼來演示我正在談論的行爲。應該發生的是,如果我發佈了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的按鈕的窗口。如果你按下按鈕兩次並等待3秒鐘,你會看到你有兩個對話框彈出另一個對話框,其中預期的行爲將是第一個對話框彈出,然後一旦它關閉,第二個對話框將彈出。

所以我的問題是:這是一個錯誤?或者我該如何緩解這個問題,以便在第一個動作用Window.ShowDialog()停止執行時,我可以只處理一個動作?

謝謝,勞爾

回答

0

一個模式對話框不會阻止處理郵件所有者窗口,否則你會看到它不能重繪模態對話框被移動了在其表面(就像一個例子) 。

爲了實現你想要的,你必須在UI線程上實現自己的隊列,可能需要一些同步來在第一個工作項到達時「喚醒它」。

編輯:

此外,如果您檢查UI線程的調用棧,而第二模態對話框,你可能會發現,它在堆棧中的第一個ShowDialog的調用它上面。

編輯#2:

有可能是這樣,沒有實現自己的隊列更簡單的方法。如果使用SynchronizationContext而不是SynchronizationContext,則可以使用Dispatcher對象,您可以調用BeginInvoke,優先級爲DispatcherPriority.Normal,它將正確排隊(檢查)。

+0

我確實創建了自己的同步上下文隊列,但我一直在尋找更優雅的解決方案。我嘗試使用優先級爲Normal的調度程序,但得到了相同的結果。我想在一天結束的時候我會改變我的UI,不使用模態對話框。你會認爲會有某種方法可以解決這個問題,但我不會。 – HaxElit 2010-05-06 14:25:50

0

我知道這是一個老問題,但因爲我在等待我的問題的答案(Advice on using the Dispatcher Priority and Binding)我認爲這將付出代價。

您遇到的情況是調度器上的嵌套抽取。我建議閱讀WPF Threading Model上的MSDN文章,尤其是標題爲「技術細節和障礙點」的部分,該部分是頁面下方的三分之二。描述嵌套抽吸的小節在下面被複制以方便起見。

嵌套抽水

有時候是不可行的完全鎖定了UI線程。 讓我們考慮一下MessageBox類的Show方法。顯示不會 返回,直到用戶單擊確定按鈕。但是,它會創建一個 窗口,該窗口必須具有消息循環才能進行交互。雖然 我們正在等待用戶單擊確定,原始應用程序 窗口不響應用戶輸入。但是,它會繼續處理畫圖消息。當 覆蓋並顯示時,原始窗口會自行重繪。

enter image description here

一些線程必須負責消息框窗口。 WPF 只爲消息框窗口創建一個新線程,但是此線程 將無法​​在原始窗口 (請記住先前對互斥的討論)中繪製禁用的元素。相反,WPF 使用嵌套消息處理系統。 Dispatcher類包含 一種稱爲PushFrame的特殊方法,它存儲應用程序的當前執行點,然後開始一個新的消息循環。當 嵌套消息循環完成後,將在原始的 PushFrame調用後繼續執行。

在這種情況下,PushFrame在調用 MessageBox.Show時維護程序上下文,並啓動一個新的消息循環來重新繪製背景窗口並處理消息框窗口的輸入。當 用戶單擊「確定」並清除彈出窗口時,嵌套循環退出,並在調用「顯示」後繼續執行 控制。