2010-04-08 81 views
4

我正在處理一個嚴重的數據綁定的Win.Forms應用程序,我發現了一些奇怪的行爲。該應用程序具有單獨的I/O線程,通過異步web請求接收更新,然後將其發送到主/ GUI線程,以處理和更新應用程序範圍的數據存儲(這又可以將數據綁定到各種GUI-元素等)。 Web請求另一端的服務器需要定期請求或會話超時。與從非GUI線程顯示MessageBox相關的問題

我已經通過處理線程的問題等幾個嘗試的解決方案走了,我觀察到以下行爲:

  1. 如果我使用Control.Invoke從我發送更新/ O-線程到主線程,此更新會導致MessageBox顯示主窗體的消息泵停止,直到用戶單擊確定按鈕。這也阻止了I/O線程繼續最終導致服務器超時。

  2. 如果我使用Control.BeginInvoke從I/O線程(或多個)發送更新主線程的主窗體的消息泵不停止,但如果更新的處理導致了一個消息是如圖所示,該更新的其餘部分的處理將暫停,直到用戶單擊確定。由於I/O線程繼續運行,並且消息泵繼續處理消息,因此可以在消息框完成之前調用幾個用於更新的BeginInvoke。這導致無法接受的失序更新。

  3. I/O線程將更新添加到阻塞隊列(非常類似於Creating a blocking Queue<T> in .NET?)。 GUI線程使用Forms.Timer定期應用阻塞隊列中的所有更新。該解決方案既解決了阻塞I/O線程的問題,又解決了更新的順序性問題,即直到完成前一個更新纔會開始。但是,性能成本很低,並且在顯示更新時引入延遲,這在長期內是不可接受的。我希望主線程中的更新處理是事件驅動的而不是輪詢。

所以對我的問題。我應該怎麼做這:

  1. 避免阻塞I/O線程
  2. 保證更新完成了序列
  3. 保持主消息泵運行,同時顯示一個消息框,爲的結果更新。

更新:請參見下面

+0

問題:必須在用戶單擊messageBox上的「確定」後才更新GUI,或者只能通知他們已發生更新,例如通過多行文本框中的消息? – Asher 2010-04-08 10:19:56

+0

它必須是一個彈出窗口,但我不確定它是否需要模態。我可以創建自己的非模態彈出窗口。 – 2010-04-08 11:14:41

回答

0

這裏是我結束了與解決方案:

  • I/O線程將所有更新放在線程安全/鎖定隊列中。
  • 獨立的工作線程無休止地分離更新,然後開始將它們調入GUI線程。
  • 在GUI-thread中響應更新顯示MessageBox現在用BeginInvoke完成。相比之前的(在3以上使用GUI的更新輪詢描述)

此解決方案具有以下優點:GUI而不是輪詢的

  1. 事件驅動的更新。這給出了(理論上)更好的性能和更少的延遲。
  2. GUI更新和I/O都不被消息框鎖定。

更新:似乎使用此解決方案顯示消息框時GUI-updates仍然被鎖定。這會在更正時更新。

更新2:通過將Invoke更改爲BeginInvoke,更新了針對工作線程的修復。

+0

從你的問題中,你聽起來像是隊列的處理只能在用戶點擊確定後繼續。或者我就是這樣讀的 – Asher 2010-04-12 14:03:05

+0

抱歉誤會。情況並非如此,但重要的是該消息是模態的並且用戶及時得到消息。 – 2010-04-12 14:47:34

1

解決方案,使你有一個複雜的數據採集和處理的過程要保持運行,但隨後你在裏面插入一個消息。線程+調用中的任何內容都不會改變MessageBox是Modal的事實,並且您必須等待它關閉,從而使整個鏈依賴於用戶點擊某個內容。

所以,擺脫MessageBox,至少在主要路徑。如果處理的一部分確實需要用戶干預,那麼該部分必須位於單獨的線程上。

+0

消息框由監聽器遠遠顯示在數據流中,用於監聽來自遠程交易系統的錯誤消息,並且必須是彈出窗口。一個可能的解決方案可能是做MessageBox。使用BeginInvoke顯示它自己,它不需要與接收更新同步。 讓我感到困惑的是,當調用(主要消息泵停止)時,MessageBox形式的行爲與源自主線程內的事件或使用BeginInvoke(即使在顯示MessageBox時消息泵繼續處理其他事件)時的行爲有所不同。 – 2010-04-08 11:20:05

+0

消息泵始終保持運行模式彈出,但事件過濾的邏輯從根本上改變。但請注意,您可以Control.Invoke一個_function_並等待結果(SendMessage)。這與BeginInvoke有所不同,它使用PostMessage。 – 2010-04-08 11:36:56

+0

並顯示另一個線程上的MessageBox需要第二個Application.Run。使用自己的窗體並使用Show而不是ShowDialog可能更容易。 – 2010-04-08 11:38:31

5

MessageBox本身泵送消息循環。這當然不會是Windows窗體消息循環。一切正常運行,但減去由Control.BeginInvoke()發佈的委託調用請求的派發。只有Windows窗體消息循環可以做到這一點。

當在UI線程上進行MessageBox.Show()調用時會發生這種情況。但不是當它在工作線程上進行時,消息隊列是每個線程屬性。如果您可以將Show調用委託給工作人員,那麼您可能會解決您的問題。

解決您的問題:

  1. 你真正想要的正好相反:工作線程應該阻止。不阻塞會導致嚴重問題,BeginInvoke派發隊列將無限制地填滿。一個可能的技巧是計算BeginInvoke調用的數量,在委託目標中倒數。使用Interlocked類。

  2. 確保BeginInvoke目標的執行順序。真正的問題可能與工作線程不同步有關。

  3. 顯示線程上的消息框。

+0

1.在這種情況下,問題是工作線程是I/O線程,並阻止它(最終)超時服務器上的會話。此外,所有收到的更新在這個應用程序中都非常重要,所以它們必須以某種方式排隊。 2.您確認BeginInvoke的執行順序是有保證的,但我不認爲它們是原子的。有關更詳細的解釋:http://www.codeproject.com/KB/cs/begininvoke.aspx 3.我認爲MessageBox總是應該在其他GUI的主/ GUI線程中使用 – 2010-04-08 12:18:34

+0

限制是某些方法只能在創建控件底層句柄的線程上運行。並不一定是主線 – Asher 2010-04-08 12:40:51

+0

嗯,你被困在一塊岩石和一個堅硬的地方之間。我必須建議你移動搖滾。不知道「原子」應該是什麼意思。調用委託目標只能在一個線程上運行*和*執行順序是有保證的,所以永遠不會存在排序或重疊問題。 MessageBox在工作線程上工作正常,但該框可能錯誤地放在Z順序中。 NotifyIcon氣球是一個更好的鼠標陷阱。 – 2010-04-08 12:41:10

1

不要使用Forms.Timer從隊列中應用更新,而是使用另一個線程來執行更新。此線程持續監視隊列並(可能)告訴GUI何時使用新數據刷新自己(通過BeginInvoke)MessageBox可以從此隊列讀取器線程顯示 - 不必是GUI線程。


編輯:隊列消費者可以撥打Control.Invoke顯示消息框來解決z順序問題

+0

這與我目前正在實施的解決方案非常相似。 – 2010-04-09 10:20:55

+1

所以給一個兄弟一個投票:) – Asher 2010-04-12 14:02:36

+0

對不起,我太晚了,但給你的評論一個投票。 :-) – 2010-04-12 14:43:56