【问题标题】:Why am I seeing a Cross-thread exception when invoking EventHandler.BeginInvoke through Delegate.Target?为什么我在通过 Delegate.Target 调用 EventHandler.BeginInvoke 时看到跨线程异常?
【发布时间】:2016-03-16 13:38:09
【问题描述】:

我正在尝试编写一个扩展方法来简化跨线程事件处理。以下是我的构想,根据我的理解,它应该可以工作;但是,当调用 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( );
            } ) ) );
        }
    }
}

这仍然引发异常的原因是什么?我怎么做错了?

【问题讨论】:

  • 你错了,委托的 BeginInvoke() 方法总是在线程池线程上运行。 Invoke() 已经足够好了。您还很难假设事件订阅者是 Winforms 或 WPF 控件,如果它只是一个普通的类,那就太糟糕了。两年后你才会发现,非常痛苦。不要太花哨。
  • @Hans 实际上,我没有做出这样的假设。如果您查看 If/Else If/Else,您将看到第一个检查它是否是 WPF 控件,第二个检查它是否是 WinForms 控件,第三个假设它不是并继续前进。如果这两者都不是,那么程序假定它是一个简单的类对象......如果我在任何不是 winforms 或 wpf 的东西中开发,这将是一个问题(此时我只需要添加更多链接到 if /then 链接或找出更好的解决方案(我不想在每个方法中都写 if InvokeRequired;这就是我这样做的原因。
  • 正确的方法是使用 SynchronizationContext.Current。很多关于它的问答。
  • @HansPassant 谢谢;当它开始失败时,我会记住这一点......

标签: c# wpf winforms async-await invoke


【解决方案1】:

您在正确的线程上调用委托 - 但委托本身随后调用 evnt.BeginInvoke,后者在线程池上执行 evnt 委托...所以您最终仍会执行 真正的 非 UI 线程上的底层委托(在本例中为 _Test,将设置背景颜色)。

您已经编组到执行委托的正确线程 - 所以只需使用 evnt(sender, e) 执行它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-08-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-01
    • 2012-04-30
    • 2017-12-23
    • 1970-01-01
    相关资源
    最近更新 更多