【问题标题】:What's the difference between InvokeAsync and BeginInvoke for WPF DispatcherWPF Dispatcher 的 InvokeAsync 和 BeginInvoke 有什么区别
【发布时间】:2012-11-04 22:43:45
【问题描述】:

我注意到在 .NET 4.5 中,WPF Dispatcher 获得了一组新的方法来执行调度程序线程上的东西,称为InvokeAsync。在 .NET 4.5 之前,我们有 InvokeBeginInvoke 分别同步和异步处理这个问题。

除了命名和可用的重载略有不同之外,BeginInvokeInvokeAsync 方法之间是否有任何主要区别?

哦,我已经查过了,都可以awaited:

private async Task RunStuffOnUiThread(Action action)
{
    // both of these works fine
    await dispatcher.BeginInvoke(action);
    await dispatcher.InvokeAsync(action);
}

【问题讨论】:

    标签: c# wpf .net-4.5 async-await


    【解决方案1】:

    异常处理不同。

    您可能需要检查以下内容:

    private async void OnClick(object sender, RoutedEventArgs e)
    {
        Dispatcher.UnhandledException += OnUnhandledException;
        try
        {
            await Dispatcher.BeginInvoke((Action)(Throw));
        }
        catch
        {
            // The exception is not handled here but in the unhandled exception handler.
            MessageBox.Show("Catched BeginInvoke.");
        }
    
        try
        {
           await Dispatcher.InvokeAsync((Action)Throw);
        }
        catch
        {
            MessageBox.Show("Catched InvokeAsync.");
        }
    }
    
    private void OnUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
    {
        MessageBox.Show("Catched UnhandledException");
    }
    
    private void Throw()
    {
        throw new Exception();
    }
    

    【讨论】:

    • 这应该被标记为正确答案。 BeginInvoke 在未处理的异常上关闭应用程序与 InvokeAsync 将异常通过返回的等待返回的事实是一个巨大的差异。
    • @jam40jeff 好点。对于那些从 BeginInvoke 迁移到 InvokeAsync 的人:想象一个巨大的遗留应用程序,其中包含对 BeginInvoke 的大量调用。如果将其重构为使用 InvokeAsync,则未处理的异常将从异常点冒出,从而关闭调用线程。另一方面,BeginInvoke 会将该异常传递给主应用程序处理程序,该处理程序会被捕获并且不会使应用程序停机。无论哪种方式,它们都不是相互替代的,当从一个切换到另一个时,测试行为上的差异。
    • 您声明异常处理是不同的,并给出一个代码示例。但是你从来没有明确说明如何异常处理是不同的。如果您添加它,您的答案质量会提高。
    • @JHBonarius 请参阅第一个异常处理程序中的注释。
    【解决方案2】:

    没有区别,因为BeginInvoke 方法调用私有LegacyBeginInvokeImpl 方法,它自己调用私有方法InvokeAsyncImplInvokeAsync 使用的方法)。所以基本上是一样的。看起来这是一个简单的重构,但奇怪的是 BeginInvoke 方法没有被标记为过时。

    开始调用:

    public DispatcherOperation BeginInvoke(DispatcherPriority priority, Delegate method)
    {
        return this.LegacyBeginInvokeImpl(priority, method, null, 0);
    }
    
    private DispatcherOperation LegacyBeginInvokeImpl(DispatcherPriority priority, Delegate method, object args, int numArgs)
    {
        Dispatcher.ValidatePriority(priority, "priority");
        if (method == null)
        {
            throw new ArgumentNullException("method");
        }
        DispatcherOperation dispatcherOperation = new DispatcherOperation(this, method, priority, args, numArgs);
        this.InvokeAsyncImpl(dispatcherOperation, CancellationToken.None);
        return dispatcherOperation;
    }
    

    InvokeAsync:

    public DispatcherOperation InvokeAsync(Action callback, DispatcherPriority priority)
    {
        return this.InvokeAsync(callback, priority, CancellationToken.None);
    }
    
    public DispatcherOperation InvokeAsync(Action callback, DispatcherPriority priority, CancellationToken cancellationToken)
    {
        if (callback == null)
        {
            throw new ArgumentNullException("callback");
        }
        Dispatcher.ValidatePriority(priority, "priority");
        DispatcherOperation dispatcherOperation = new DispatcherOperation(this, priority, callback);
        this.InvokeAsyncImpl(dispatcherOperation, cancellationToken);
        return dispatcherOperation;
    }
    

    【讨论】:

    • 目前,我正在使用 BeginInvoke(触发调度程序上的 DispatcherUnhandledException 和 AppDomain.CurrentDomain.UnhandledException)按预期工作的未处理异常,但 InvokeAsync 上的未处理异常正在被默默吞下。从 InvokeAsync 继续执行任务并捕获异常似乎是一种有效的解决方法。
    • BeginInvoke 在 .NET 中使用 BeginSomethingEndSomething 方法进行异步操作的 "Asynchronous Programming Model" 之后的模式。大概这就是为什么它没有被指定为弃用或过时的原因。除了Begin/End 约定用于使用IAsyncResult,而BeginInvoke 没有,也没有EndInvoke,所以它首先是多余的。
    • 从构造函数返回的 DispatcherOpartion 有一个成员域 useAsyncSematics,它是根据使用的构造函数设置的。这会导致使用 async 和 await 时的异常处理有所不同。看我的回答。
    【解决方案3】:

    方法签名有区别:

    BeginInvoke(Delegate, Object[])
    InvokeAsync(Action)
    

    对于BeginInvoke(),编译器隐式创建数组Object[],而对于InvokeAsync(),不需要这样的数组:

    IL_0001:  ldarg.0
    IL_0002:  call       instance class [WindowsBase]System.Windows.Threading.Dispatcher [WindowsBase]System.Windows.Threading.DispatcherObject::get_Dispatcher()
    IL_0007:  ldarg.1
    IL_0008:  ldc.i4.0
    IL_0009:  newarr     [mscorlib]System.Object
    IL_000e:  callvirt   instance class [WindowsBase]System.Windows.Threading.DispatcherOperation [WindowsBase]System.Windows.Threading.Dispatcher::BeginInvoke(class [mscorlib]System.Delegate, object[])
    
    
    IL_0014:  ldarg.0
    IL_0015:  call       instance class [WindowsBase]System.Windows.Threading.Dispatcher [WindowsBase]System.Windows.Threading.DispatcherObject::get_Dispatcher()
    IL_001a:  ldarg.1
    IL_001b:  callvirt   instance class [WindowsBase]System.Windows.Threading.DispatcherOperation [WindowsBase]System.Windows.Threading.Dispatcher::InvokeAsync(class [mscorlib]System.Action)
    

    【讨论】:

      【解决方案4】:

      嗯,我注意到的一个区别是 InvokeAsync 有一个通用重载,它返回一个 DispatcherOperation 作为返回值,并接受一个 Func 作为其委托输入参数。因此,您可以通过 InvokeAsync 以一种类型安全的方式检索操作的结果,类似于等待任务结果的方式。

      【讨论】:

        猜你喜欢
        • 2012-07-26
        • 1970-01-01
        • 2020-01-17
        • 2012-09-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-02-11
        • 1970-01-01
        相关资源
        最近更新 更多