C# 为什么通过Delegate.Target调用EventHandler.BeginInvoke时会出现跨线程异常?
我正在尝试编写一个扩展方法,它将简化跨线程事件处理。以下是我的构想,据我理解,它应该会起作用;然而,当调用EndInvoke方法时,我得到了一个跨线程异常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 {
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谢谢;当这开始失败时,我会记住这一点。。。