2011-04-06 54 views
3

我試圖編寫處理試圖訪問從另一個線程控制時可能發生的所有情況/問題「SafeInvoke」方法的訪問控制。我已經看到了很多解決方案和很多關於這個問題的問題,雖然對於大多數人來說有一些是足夠好的,但他們都沒有考慮到競爭條件(意味着它仍然有可能獲得不需要的例外)。創建方式安全地從另一個線程

所以這是我到目前爲止,我想是最好的,我可以,爲什麼我把一些IFS和嘗試捕獲評論。我也試圖只捕獲相關的異常,InvalidOperationException是一個可能出於各種各樣的原因(包括Collection被修改)並且我不想壓制這些異常(因爲它們與安全調用無關)的異常。爲了檢查我是否基於異常的TargetSite.Name屬性,我還查找了反射器中的實際拋出,以查看是否有其他可能導致異常的位置。

/// <summary> 
/// Safely invokes an action on the thread the control was created on (if accessed from a different thread) 
/// </summary> 
/// <typeparam name="T">The return type</typeparam> 
/// <param name="c">The control that needs to be invoked</param> 
/// <param name="a">The delegate to execute</param> 
/// <param name="spinwaitUntilHandleIsCreated">Waits (max 5sec) until the the control's handle is created</param> 
/// <returns>The result of the given delegate if succeeded, default(T) if failed</returns> 
public static T SafeInvoke<T>(this Control c, Func<T> a, bool spinwaitUntilHandleIsCreated = false) 
{ 
    if (c.Disposing || c.IsDisposed) // preliminary dispose check, not thread safe! 
     return default(T); 

    if (spinwaitUntilHandleIsCreated) // spin wait until c.IsHandleCreated is true 
    { 
     if (!c.SpinWaitUntilHandleIsCreated(5000)) // wait 5sec at most, to prevent deadlock 
      return default(T); 
    } 

    if (c.InvokeRequired) // on different thread, need to invoke (can return false if handle is not created) 
    { 
     try 
     { 
      return (T)c.Invoke(new Func<T>(() => 
      { 
       // check again if the control is not dispoded and handle is created 
       // this is executed on the thread the control was created on, so the control can't be disposed 
       // while executing a() 
       if (!c.Disposing && !c.IsDisposed && c.IsHandleCreated) 
        return a(); 
       else // the control has been disposed between the time the other thread has invoked this delegate 
        return default(T); 
      })); 
     } 
     catch (ObjectDisposedException ex) 
     { 
      // sadly the this entire method is not thread safe, so it's still possible to get objectdisposed exceptions because the thread 
      // passed the disposing check, but got disposed afterwards. 
      return default(T); 
     } 
     catch (InvalidOperationException ex) 
     { 
      if (ex.TargetSite.Name == "MarshaledInvoke") 
      { 
       // exception that the invoke failed because the handle was not created, surpress exception & return default 
       // this is the MarhsaledInvoke method body part that could cause this exception: 
       // if (!this.IsHandleCreated) 
       // { 
       //  throw new InvalidOperationException(SR.GetString("ErrorNoMarshalingThread")); 
       // } 
       // (disassembled with reflector) 
       return default(T); 
      } 
      else // something else caused the invalid operation (like collection modified, etc.) 
       throw; 
     } 
    } 
    else 
    { 
     // no need to invoke (meaning this is *probably* the same thread, but it's also possible that the handle was not created) 
     // InvokeRequired has the following code part: 
     //  Control wrapper = this.FindMarshalingControl(); 
     //  if (!wrapper.IsHandleCreated) 
     //  { 
     //   return false; 
     //  } 
     // where findMarshalingControl goes up the parent tree to look for a parent where the parent's handle is created 
     // if no parent found with IsHandleCreated, the control itself will return, meaning wrapper == this and thus returns false 
     if (c.IsHandleCreated) 
     { 
      try 
      { 
       // this will still yield an exception when the IsHandleCreated becomes false after the if check (race condition) 
       return a(); 
      } 
      catch (InvalidOperationException ex) 
      { 
       if (ex.TargetSite.Name == "get_Handle") 
       { 
        // it's possible to get a cross threadexception 
        // "Cross-thread operation not valid: Control '...' accessed from a thread other than the thread it was created on." 
        // because: 
        // - InvokeRequired returned false because IsHandleCreated was false 
        // - IsHandleCreated became true just after entering the else bloc 
        // - InvokeRequired is now true (because this can be a different thread than the control was made on) 
        // - Executing the action will now throw an InvalidOperation 
        // this is the code part of Handle that will throw the exception 
        // 
        //if ((checkForIllegalCrossThreadCalls && !inCrossThreadSafeCall) && this.InvokeRequired) 
        //{ 
        // throw new InvalidOperationException(SR.GetString("IllegalCrossThreadCall", new object[] { this.Name })); 
        //} 
        // 
        // (disassembled with reflector) 
        return default(T); 
       } 
       else // something else caused the invalid operation (like collection modified, etc.) 
        throw; 
      } 
     } 
     else // the control's handle is not created, return default 
      return default(T); 
    } 
} 

有一件事我不確定,如果IsHandleCreated = true,它會再次變成虛假嗎?

我爲IsHandleCreated添加了spinwait,因爲我在控件的OnLoad事件中啓動了任務<> s,並且可能在控件加載完成之前完成了任務。然而,如果加載控件需要花費超過5秒的時間,我仍然可以完成任務,而無需更新GUI(否則我會有很多線程等待可能不會再發生的事情)

如果您有任何優化建議或找到任何可能仍然存在問題的錯誤或場景,請讓我知道:)。

回答

0

老實說,你當你在一個基本的應用程序訪問控制/從UI線程你這樣做可能checkings呢?可能不會,你只是編碼,並期望控制存在,並沒有被處置。你爲什麼這麼多的檢查呢?

這不是一個好主意,讓多個線程訪問用戶界面,但如果你有沒有其他辦法,我會建議你使用Control.BeginInvoke。使用Control.BeginInvokeControl.IsInvokeRequired應該就夠了。

其實我從來沒有用過Control.IsInvokeRequired,我手裏所訪問將來自不同的線程並沒有之前知道。

+0

我最近在升級工作框架以異步方式獲取數據來填充一個網格。我有一個GridOverview抽象類與抽象方法來檢索數據。數據的實際獲取從概述加載時開始(我重寫了OnLoad)。我不能總是調用,因爲當句柄沒有創建時失敗。我不能總是假定代碼不是同步使用的。我在一個由4人組成的團隊中,所以我不能依靠它,它總是以相同的方式使用。 – drake7707 2011-04-06 17:54:22

+0

此外,當我只需要更新網格時我使用BeginInvoke,並且我不需要從控件獲取值,但是數據獲取有時基於下拉菜單或類似輸入的SelectedIndex。此外BeginInvoke不會被解僱調用線程已完成,我需要依賴的事實是,任務完成後,網格將始終更新。 – drake7707 2011-04-06 18:00:12

+0

你說得對,雖然最好先取值,然後以上述值作爲參數啓動任務,而不是讓任務取而代之。但有些情況並非如此簡單,我不想每次都別無選擇,只能在處理所有場景時使用調用,以免在某個隨機點崩潰。 – drake7707 2011-04-06 18:13:58

相關問題