C# 创建从另一个线程安全访问控件的方法

C# 创建从另一个线程安全访问控件的方法,c#,multithreading,C#,Multithreading,我正在尝试编写一个“SafeInvoke”方法,用于处理从另一个线程访问控件时可能发生的所有情况/问题。我已经看到了很多关于这方面的解决方案和很多问题,虽然有一些对大多数人来说已经足够好了,但他们都没有考虑到种族条件(这意味着仍然有可能出现不想要的例外) 这就是我到目前为止所做的,我试着尽我所能去评论为什么我会提出一些假设并尝试捕捉。我还试图只捕获相关的异常,InvalidOperationException是一种可能由于各种原因(包括集合被修改)而发生的异常,我不想抑制这些异常(因为它们与安全

我正在尝试编写一个“SafeInvoke”方法,用于处理从另一个线程访问控件时可能发生的所有情况/问题。我已经看到了很多关于这方面的解决方案和很多问题,虽然有一些对大多数人来说已经足够好了,但他们都没有考虑到种族条件(这意味着仍然有可能出现不想要的例外)

这就是我到目前为止所做的,我试着尽我所能去评论为什么我会提出一些假设并尝试捕捉。我还试图只捕获相关的异常,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);
    }
}
//
///安全地在创建控件的线程上调用操作(如果从其他线程访问)
/// 
///返回类型
///需要调用的控件
///要执行的委托
///等待(最多5秒),直到创建控件的句柄
///如果成功,则为给定委托的结果;如果失败,则为默认值(T)
public static T SafeInvoke(此控件c、Func a、bool spinwaitUntilHandleIsCreated=false)
{
if(c.Disposing | | c.IsDisposed)//预处理检查,非线程安全!
返回默认值(T);
if(spinwaituntlhandleiscreated)//自旋等待,直到c.IsHandleCreated为true
{
如果(!c.SpinWaitUntilHandleIsCreated(5000))//最多等待5秒,以防止死锁
返回默认值(T);
}
if(c.invokererequired)//在不同的线程上,需要调用(如果未创建句柄,则可能返回false)
{
尝试
{
返回(T)c.Invoke(新函数(()=>
{
//再次检查是否未对控件进行dispode并创建句柄
//这是在创建控件的线程上执行的,因此无法释放该控件
//执行()时
如果(!c.Disposing&&!c.IsDisposed&&c.IsHandleCreated)
返回a();
else//在另一个线程调用此委托期间,控件已被释放
返回默认值(T);
}));
}
捕获(ObjectDisposedException ex)
{
//遗憾的是,整个方法不是线程安全的,因此仍然可以获取objectdisposed异常,因为线程
//通过了处置检查,但后来被处置。
返回默认值(T);
}
捕获(无效操作异常ex)
{
if(ex.TargetSite.Name==“MarshaledInvoke”)
{
//由于未创建句柄而导致调用失败的异常,超出异常并返回默认值
//这是可能导致此异常的MarhsaledInvoke方法主体部分:
//如果(!this.IsHandleCreated)
//   {
//抛出新的InvalidOperationException(SR.GetString(“ErrorNoMarshalingRead”);
//   }
//(与反射器一起拆卸)
返回默认值(T);
}
else//其他原因导致了无效操作(如修改了集合等)
投掷;
}
}
其他的
{
//无需调用(这意味着这可能是同一个线程,但也可能没有创建句柄)
//InvokeRequired具有以下代码部分:
//控件包装器=this.FindMarshalingControl();
//如果(!wrapper.IsHandleCreated)
//        {
//返回false;
//        }
//其中findMarshalingControl在父树上查找创建父句柄的父级
//如果没有找到IsHandleCreated的父级,控件本身将返回,这意味着wrapper==this,因此返回false
如果(c.IsHandleCreated)
{
尝试
{
//当IsHandleCreated在if检查(竞争条件)后变为false时,这仍然会产生异常
返回a();
}
捕获(无效操作异常ex)
{
if(ex.TargetSite.Name==“获取句柄”)
{
//可能会出现交叉线程异常
//跨线程操作无效:从创建控件的线程以外的线程访问控件“…”
//因为:
//-InvokeRequired返回false,因为IsHandleCreated为false
//-IsHandleCreated在进入else集团后成为现实
//-InvokeRequired现在为true(因为这可能是一个不同于控件的线程)
//-执行该操作将引发InvalidOperation
//这是将引发异常的句柄的代码部分
//
//if((checkForIllegalCrossThreadCalls&!inCrossThreadSafeCall)&&this.InvokeRequired)
//{
//抛出新的InvalidOperationException(SR.GetString(“IllegalCrossThreadCall”,新对象[]{this.Name}));
//}
//
//(与反射器一起拆卸)
返回默认值(T);
}
else//其他原因导致了无效操作(如修改了集合等)
投掷;
}
}
否则//未创建控件的句柄,返回默认值