WPF调度程序中InvokeAsync和BeginInvoke有什么区别?

83

我注意到在.NET 4.5中,WPF Dispatcher增加了一组新方法,用于在线程上执行调度程序的操作,名为InvokeAsync。在.NET 4.5之前,我们有 InvokeBeginInvoke,分别处理同步和异步。

除了名称和可用的略微不同的重载之外,BeginInvokeInvokeAsync 方法之间是否有任何重大差异?

哦,我已经检查过了,两者都可以使用await

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

68

异常处理方式不同。

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

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();
}

16
应该将其标记为正确答案。 BeginInvoke 在未处理的异常时会导致应用程序崩溃,而 InvokeAsync 则通过返回的可等待对象将异常传递回来,这是一个巨大的区别。 - jam40jeff
1
@jam40jeff 好观点。对于那些从 BeginInvoke 迁移到 InvokeAsync 的人来说:想象一个有大量调用 BeginInvoke 的遗留应用程序。如果将其重构为使用 InvokeAsync,那么未处理的异常将从异常点冒泡出来,使调用线程崩溃。另一方面,BeginInvoke 会将异常传递到主应用程序处理程序中,被捕获并不会使应用程序崩溃。无论哪种方式,它们都不能完全替换彼此,在从一个切换到另一个时,测试行为上的差异。 - Contango
你提到异常处理不同,并给出了一个代码示例。但是你从未明确说明异常处理的不同之处在哪里。如果你能补充这一点,你的回答质量会更好。 - JHBonarius
@JHBonarius,请查看第一个异常处理程序中的注释。 - Wouter

53

BeginInvoke方法中,调用了私有的LegacyBeginInvokeImpl方法,它本身又调用了私有方法InvokeAsyncImplInvokeAsync使用的方法)。因此,它们基本上是一样的。这似乎是一个简单的重构,然而奇怪的是BeginInvoke方法没有被标记为过时。

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;
}

10
目前我正在使用BeginInvoke,按预期处理未处理的异常(触发调度程序上的DispatcherUnhandledException和AppDomain.CurrentDomain.UnhandledException),但是在InvokeAsync中,未处理的异常被默默地吞掉了。通过在InvokeAsync中继续任务,并使用某些方法来捕获异常似乎是一种有效的解决方法。 - Lamarth
4
BeginInvoke是.NET中基于“异步编程模型”的一种方法,该模型使用BeginSomethingEndSomething方法来执行异步操作。这可能是它没有被标记为"已弃用"或"过时"的原因。但是,Begin/End约定是用于使用IAsyncResult,而BeginInvoke并没有使用它,也没有任何EndInvoke,所以它从一开始就有些多余。 - sidbushes
4
构造函数返回的 DispatcherOperation 对象有一个成员字段 useAsyncSemantics,根据使用的构造函数进行设置。这会导致在使用异步和等待时异常处理方式的差异。请参见我的回答。 - Wouter
这个回答完全错误。在这两种情况下,异常处理是不同的。请参考@Wouter的回答。 - undefined

17

方法签名存在差异:

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)

3

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接