2011-04-21 29 views
1

我有一個類型的控件,包含一個GridView和一些實用程序按鈕。該控件在我的應用程序中隨處可見。它通過代表異步填充:試圖避免例外時調用回UI線程

protected virtual void PopulateGridView() 
    { 
     if (isPopulating) return; 

     //a delegate given to the control by its parent form 
     if (GetterMethod != null) 
     { 
      isPopulating = true; 
      /*unimportant UI fluff here*/ 

      //some controls are fast enough to not have to mess with threading 
      if(PopulateSynchronously) 
      { 
       PopulateWithGetterMethod(); 
       InitializeGridView(); 
      } 
      else //most aren't 
      { 
       Action asyncMethod = PopulateWithGetterMethod; 
       asyncMethod.BeginInvoke(
        ar => Invoke((MethodInvoker)InitializeGridView)), null); 
      } 
     } 
    } 

    private void PopulateWithGetterMethod() 
    { 
     //a list of whetever the control is displaying; 
     //the control ancestor and this collection are generic. 
     RetrievedInformation = GetterMethod(); 
    } 

    protected virtual void InitializeGridView() 
    { 
     //use RetrievedInformation to repopulate the GridView; 
     //implementation not important, except it touches UI elements, 
     //so it needs to be called from the worker thread using Invoke. 
    } 

對於長時間運行的查詢,有時用戶會感到不耐煩並關閉窗口。或者,當其中一個控件基於定時器自動刷新時,用戶會偶然關閉一個窗口。當發生這種情況並且查詢DID完成時,回調委託中的Invoke調用將因InvalidOperationException失敗,因爲該控件沒有窗口句柄。

爲了解決這個問題,我試圖使用內置IsHandleCreated屬性:

... 
      else 
      { 
       Action asyncMethod = PopulateWithGetterMethod; 
       asyncMethod.BeginInvoke(
        ar => { if(IsHandleCreated) 
           Invoke((MethodInvoker)InitializeGridView)); 
         }, null); 
      } 

然而,異常仍然發生,只是不經常。我設法重現它,並發現Invoke調用仍然發生,即使IsHandleCreated上的監視顯示爲false。我的猜測是線程在檢查和Invoke調用之間被搶佔,就像你在提升它之前檢查一個事件委託爲null一樣。

我還是有選擇,我想,但我想知道什麼是最好的是:

  • 檢查不僅IsHandleCreated,但處置,以確保控制真的是活得很好,而不僅僅是即將被銷燬。
  • 在進行檢查之前執行一個Thread.Yield(),以便操作系統有機會在檢查句柄之前執行任何窗口管理。
  • 將try調用封裝在try/catch中,以抑制任何InvalidOperationException異常,或者至少有一個報告缺少窗口句柄。老實說,在這種情況下,我不在乎GridView不能更新;用戶關閉了窗戶,顯然他們不在乎。讓線程安靜地死去,而不會取消整個應用程序。

第三個選項看起來像是一個cop-out;必須有一個更清潔的方式來處理它。但是,我不確定其他兩個中的任何一個都是100%修復。

編輯:檢查處置和IsDisposed也不工作;我在if條件塊中拋出了一個異常,條件爲「IsHandleCreated & &!Disposing & &!IsDisposed」,其中第一個和最後一個節點在觀察時爲假。目前,我正在使用消息「在調用窗口句柄之前無法在控件上調用Invoke或BeginInvoke」來捕獲所有異常,這正是我希望不要這樣做的。

回答

4

是的,有一個100%乾淨的方法來做到這一點:在允許窗體關閉之前終止線程。除此之外,任何其他的東西都是一種創傷手段的幫手,檢查表單是否仍然存在是一種無法解決的不可避免的競爭條件。當你調用Invoke()時,你只能最小化表單是奇聞趣事的機率,你不能消除它們。

檢查this answer的模式。

+0

優雅,我大概可以找出一些會沿着這些線路工作的東西。但是,我的情況並不那麼簡單,異步代碼封裝在UserControl中,而不是Form。在一個窗口中可以有幾個,所以解決方案必須確保所有線程都已執行。最後,這個錯誤的頭號原因是用戶要麼厭倦了,要麼意識到他們犯了一個錯誤,然後點擊X;在用戶想要它離開後保持窗口不是理想的用戶體驗。這些都不是不可逾越的。 – KeithS 2011-04-21 17:39:28

+0

我們遇到了同樣的問題,當我們從後臺線程顯示錯誤消息框時,Keith正在描述併吞下異常。沒有吞沒例外,真的沒辦法解決這個問題。除此之外,Windows句柄的處理順序不一定是它們的創建順序,因此您不能依賴邏輯流程來擺脫這種情況。即使在你的例子中,你正在使用mCompleted和mClosePending進行比賽。 – 2011-04-21 19:03:50

+0

不,這些變量只能在UI線程上訪問過。那不能比賽。 – 2011-04-21 19:19:36

1

處置是你最好的選擇;但是,我們每隔一段時間就會遇到同樣的問題,有時Disposing調用返回false,但是在我們嘗試使用它所處理的控件時,即使它在3行之後。

在這些情況下,我們發現最好是捕獲特定異常並驗證異常文本是否包含我們正在尋找的內容。如果它是一個例外,這是一個已知的例外,那麼吞嚥它並不是一個可怕的想法。事情是確保你只是吞下你正在尋找的特定異常。