2011-04-06 13 views
3

다른 스레드에서 컨트롤에 액세스하려고 할 때 발생할 수있는 모든 사례/문제를 처리하는 'SafeInvoke'메서드를 작성하려고합니다. 나는 이것에 대한 많은 해결책과 많은 질문을 보았으며, 대부분의 사람들에게 충분할만큼 좋은 것들도 있지만, 그들은 모두 경합 조건을 고려하지 않았다. (이는 원치 않는 예외를 얻을 수 있음을 의미한다.)다른 스레드에서 컨트롤에 안전하게 액세스하는 방법 만들기

이것은 내가 지금까지 가지고있는 것입니다. 나는 최선을 다해서 ifs를 넣고 캐치를 시도 할 수있는만큼 최선을 다했습니다. 나 또한 관련 예외 만 잡으려고했는데 InvalidOperationException은 콜렉션이 수정 된 것을 포함하여 다양한 이유로 발생할 수 있으며 (안전 호출과 아무 관련이 없기 때문에)이를 억제하고 싶지 않습니다. 예외의 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 =, 그것은 다시 거짓이 될 것이다 경우이다 나는 확실히 알 수없는 한 가지가있다?

컨트롤의 OnLoad 이벤트에서 작업 <을 시작했기 때문에 IsHandleCreated에 대한 spinwait을 추가했으며 컨트롤이 완전히로드되기 전에 작업이 완료되었을 수 있습니다. 그러나 컨트롤을로드하는 데 5 초 이상이 걸리면 GUI를 업데이트하지 않고 작업을 완료하게합니다 (그렇지 않으면 더 이상 발생하지 않는 스레드에 대해 많은 시간이 걸릴 것입니다)

최적화에 대한 제안이나 여전히 문제가 될 수있는 버그 또는 시나리오를 찾으려면 알려주십시오 :).

답변

0

기본 응용 프로그램의 UI 스레드를 사용하여 컨트롤 /에 액세스하면 솔직히 검사 할 수 있습니까? 아마, 당신은 코드를 작성하고 컨트롤이 존재하고 폐기되지 않을 것이라고 기대합니다. 너 왜 그런 양의 검사를 한거야?

여러 스레드가 UI에 액세스하도록하는 것은 좋지 않지만 다른 방법이없는 경우에는 Control.BeginInvoke을 사용하는 것이 좋습니다. Control.BeginInvoke을 사용하면 Control.IsInvokeRequired이면 충분합니다.

실제로 나는 결코 Control.IsInvokeRequired을 사용하지 않았습니다. 어떤 스레드가 다른 스레드에서 올지 알 수 있습니다.

+0

최근 작업에서 프레임 워크를 업그레이드하여 데이터를 비동기 적으로 가져와 그리드를 채 웁니다. 데이터를 검색하는 몇 가지 추상 메서드가있는 GridOverview 추상 클래스가 있습니다. 개요가로드 될 때 데이터의 실제 가져 오기가 시작됩니다 (OnLoad를 재정의하는 중입니다). 핸들이 만들어지지 않을 때 실패하기 때문에 항상 호출 할 수는 없습니다. 나는 항상 코드가 동 기적으로 사용되지 않는다고 가정 할 수는 없다. 저는 4 명으로 구성된 팀에 있기 때문에 항상 같은 방식으로 사용될 것입니다. – drake7707

+0

또한 Grid를 업데이트 할 필요가있을 때 BeginInvoke를 사용하고 컨트롤에서 값을 가져올 필요가 없지만 때때로 데이터 가져 오기는 드롭 다운이나 유사한 입력의 SelectedIndex를 기반으로합니다. 또한 BeginInvoke는 호출 스레드가 완료되면 해고되지 않으며 작업이 완료된 후에 그리드가 항상 업데이트된다는 사실에 의존해야합니다. – drake7707

+0

값을 먼저 가져온 다음 작업이 값을 가져 오는 대신 매개 변수로 작업을 시작하는 것이 더 나을지도 모르겠지만 맞습니다. 그러나 그렇게하기가 쉽지 않은 경우가 있습니다. 다른 선택의 여지가 없지만 모든 시나리오를 처리 할 때 invoke를 사용할 때마다 신경 쓰지 않으므로 임의의 지점에서 충돌하지 않습니다. – drake7707

관련 문제