C# 为什么通过Delegate.Target调用EventHandler.BeginInvoke时会出现跨线程异常?

C# 为什么通过Delegate.Target调用EventHandler.BeginInvoke时会出现跨线程异常?,c#,wpf,winforms,async-await,invoke,C#,Wpf,Winforms,Async Await,Invoke,我正在尝试编写一个扩展方法,它将简化跨线程事件处理。以下是我的构想,据我理解,它应该会起作用;然而,当调用EndInvoke方法时,我得到了一个跨线程异常 using System; using System.Linq; using System.Runtime.Remoting.Messaging; using System.Threading.Tasks; using System.Windows; using System.Windows.Media; namespace SCV {

我正在尝试编写一个扩展方法,它将简化跨线程事件处理。以下是我的构想,据我理解,它应该会起作用;然而,当调用EndInvoke方法时,我得到了一个跨线程异常

using System;
using System.Linq;
using System.Runtime.Remoting.Messaging;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;

namespace SCV {
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window {

        private static event EventHandler _Test;

        public static event EventHandler Test {
            add { MainWindow._Test += value; }
            remove{ MainWindow._Test -= value; }
        }

        private static async Task OnTest( ) {
            if ( MainWindow._Test != null )
                await MainWindow._Test.ExecuteAsync( null, EventArgs.Empty );
        }

        private LinearGradientBrush brshSomeBrush = new LinearGradientBrush(Colors.Red, Colors.Black, new Point(0, 0), new Point(1, 1));

        public MainWindow( ) {
            InitializeComponent( );
            MainWindow.Test += ( S, E ) => this.Background = this.brshSomeBrush;
                this.Loaded += async ( S, E ) => await MainWindow.OnTest( );
            }
    }

    static class Extensions {
        public static async Task ExecuteAsync( this EventHandler eH, object sender, EventArgs e ) {
            await Task.WhenAll( eH.GetInvocationList( ).Cast<EventHandler>( ).Select( evnt => Task.Run( ( ) => {
                System.Windows.Controls.Control wpfControl;
                System.Windows.Forms.Control formControl;
                Action begin = ( ) => evnt.BeginInvoke( sender, e, IAR => ( ( IAR as AsyncResult ).AsyncDelegate as EventHandler ).EndInvoke( IAR ), null );
                if ( evnt.Target is System.Windows.Controls.Control && !( wpfControl = evnt.Target as System.Windows.Controls.Control ).Dispatcher.CheckAccess( ) )
                    wpfControl.Dispatcher.Invoke( begin );
                else if ( evnt.Target is System.Windows.Forms.Control && ( formControl = evnt.Target as System.Windows.Forms.Control ).InvokeRequired )
                    formControl.Invoke( begin );
                else
                    begin( );
            } ) ) );
        }
    }
}
使用系统;
使用System.Linq;
使用System.Runtime.Remoting.Messaging;
使用System.Threading.Tasks;
使用System.Windows;
使用System.Windows.Media;
名称空间SCV{
/// 
///MainWindow.xaml的交互逻辑
/// 
公共部分类主窗口:窗口{
私有静态事件EventHandler\u测试;
公共静态事件事件处理程序测试{
添加{MainWindow.\u Test+=value;}
删除{MainWindow.\u Test-=value;}
}
私有静态异步任务OnTest(){
如果(主窗口。\u测试!=null)
等待主窗口。\u Test.ExecuteAsync(null,EventArgs.Empty);
}
private LinearGradientBrush brshSomeBrush=新的LinearGradientBrush(Colors.Red,Colors.Black,新点(0,0),新点(1,1));
公共主窗口(){
初始化组件();
MainWindow.Test+=(S,E)=>this.Background=this.brshSomeBrush;
this.Loaded+=async(S,E)=>await MainWindow.OnTest();
}
}
静态类扩展{
公共静态异步任务ExecuteAsync(此EventHandler eH、对象发送方、EventArgs e){
等待Task.WhenAll(eH.GetInvocationList().Cast().Select(evnt=>Task.Run(()=>{
System.Windows.Controls.Control wpfControl;
System.Windows.Forms.Control formControl;
Action begin=()=>evnt.BeginInvoke(发送方,e,IAR=>((IAR作为AsyncResult)。AsyncDelegate作为EventHandler)。EndInvoke(IAR),null);
if(evnt.Target为System.Windows.Controls.Control&!(wpfControl=evnt.Target为System.Windows.Controls.Control)。Dispatcher.CheckAccess())
wpfControl.Dispatcher.Invoke(begin);
else if(evnt.Target为System.Windows.Forms.Control&&(formControl=evnt.Target为System.Windows.Forms.Control).InvokeRequired)
调用(开始);
其他的
开始();
} ) ) );
}
}
}

这仍然抛出异常的原因是什么?我怎么会这样做呢?

您在正确的线程上调用委托-但委托本身随后调用
evnt.BeginInvoke
,它在线程池上执行
evnt
委托。。。因此,您仍然会在非UI线程上执行真正的底层委托(在本例中,
\u Test
,将设置背景颜色)


您已经封送到要在其上执行委托的正确线程-因此只需使用
evnt(sender,e)

在正确线程上调用委托即可执行它-但委托本身随后调用
evnt.BeginInvoke
,它在线程池上执行
evnt
委托。。。因此,您仍然会在非UI线程上执行真正的底层委托(在本例中,
\u Test
,将设置背景颜色)


您已经封送到要在其上执行委托的正确线程,因此只需使用
evnt(sender,e)

执行它即可。如果您弄错了,委托的BeginInvoke()方法始终在线程池线程上运行。Invoke()已经足够好了。您还很难假设事件订阅者是Winforms或WPF控件,如果它只是一个普通类,那将是一个大麻烦。你只会发现两年后,非常痛苦。“别太花哨了!”汉斯,事实上,我不这么认为。如果您查看If/Else If/Else,您将看到第一个检查它是否是WPF控件,第二个检查它是否是WinForms控件,第三个假设两者都不是并向前移动。如果这两个都不是,那么程序会假设它是一个简单的类对象。。。如果我在任何不是winforms或wpf的开发环境中开发,这将是一个问题(在这一点上,我只需要添加更多到if/then链的链接,或者找到更好的解决方案)(我不想在每个方法中都需要调用时进行编写;这就是我这样做的原因。正确的方法是使用SynchronizationContext.Current。关于它有很多问题需要解答。@HansPassant谢谢;当这开始失败时,我会记住这一点……你搞错了,委托的BeginInvoke()方法总是在线程池线程上运行。Invoke()足够好了。你还很难假设事件订阅者是Winforms或WPF控件,如果它只是一个普通类,那将是一个很大的麻烦。你只会发现两年后,这非常痛苦。不要做得太花哨。@Hans,事实上,我不这样假设。如果你看看if/Else if/Else,你会发现那不是他首先检查它是否是一个WPF控件,第二个检查它是否是一个WinForms控件,第三个假设它既不是也不是,然后向前移动。如果这两者都不是,那么程序会假设它是一个简单的类对象…如果我在任何不是WinForms或WPF的东西中开发,这将是一个问题(在这一点上,我只需要向if/then链添加更多链接,或者找到更好的解决方案(我不想在每个方法中都需要调用时进行编写;这就是我这样做的原因。正确的方法是使用SynchronizationContext.Current。对此有很多疑问。@HansPassant谢谢;当这开始失败时,我会记住这一点。。。