一般认为 WPF 的 Dispatcher 的 InvokeAsync 方法是 BeginInvoke 方法的平替方法和升级版,接近在任何情况下都应该在业务层使用 InvokeAsync 方法代替 BeginInvoke 方法。然而在异常的处理上,这两个方法还是有细微的差别的,不能说是坏事,依然可以认为使用 InvokeAsync 方法代替 BeginInvoke 方法是正确的。本文将记录这两个在抛出异常时,进入的统一异常处理事件的差别

简单来说是在 InvokeAsync 抛出未捕获的异常,将会进入到 TaskScheduler.UnobservedTaskException 事件里面。在 BeginInvoke 抛出未捕获的异常,将会进入到 Dispatcher.UnhandledException 事件里面

根据通用的 dotnet 知识可以知道,进入到 TaskScheduler.UnobservedTaskException 的异常,在 .NET Framework 4.5 之后,包含 dotnet core 和 dotnet 5 和 dotnet 6 以及更高版本,是不会导致应用程序退出进程

根据通用的 WPF 知识可以知道,进入到 Dispatcher.UnhandledException 的异常,取决于参数的 Handled 属性是否被设置为 true 值,决定是否将异常抛到线程顶层从而可能导致应用程序退出进程

通过此可以了解到,使用 InvokeAsync 和 BeginInvoke 所抛出的未捕获异常所进入的事件不相同。这里值得说明的是,无论是 InvokeAsync 或 BeginInvoke 方法,都没有使用其返回值。进一步的说明就是不对 InvokeAsync 使用 await 等待的前提下,表现行为如本文描述。本文开始的说法是严谨的,因为对 InvokeAsync 使用 await 等待,则将 InvokeAsync 异常交给 await 这一端,然后取决于等待的逻辑的异常处理,此时和 InvokeAsync 行为无关

有一些不符合我开始预期的是 InvokeAsync 抛出未捕获的异常,将会进入到 TaskScheduler.UnobservedTaskException 事件里面。这是因为 InvokeAsync 走的是 Task 封装。在 dotnet 里面,如果 Task 里存在异常,且此 Task 没有任何的 await 将会在此 Task 被回收清理时,将异常记录到 TaskScheduler.UnobservedTaskException 事件

接下来是对此行为的测试代码

新建一个 WPF 项目,编写简单的界面,加上两个按钮,这两个按钮用来分别调用 InvokeAsync 和 BeginInvoke 抛出异常

<Window x:Class="GifellichelNurcikaifallhane.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:GifellichelNurcikaifallhane"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<TextBlock x:Name="TextBlock" Margin="10,10,10,10"
TextWrapping="Wrap"/>
<StackPanel Grid.Row="1" Orientation="Horizontal" VerticalAlignment="Top">
<Button x:Name="InvokeAsyncButton" Margin="10,10,10,10"
Click="InvokeAsyncButton_OnClick">InvokeAsync</Button>
<Button x:Name="BeginInvokeButton" Margin="10,10,10,10"
Click="BeginInvokeButton_OnClick">BeginInvoke</Button>
</StackPanel>
</Grid>
</Window>

在 MainWindow 的构造函数里面,分别添加两个异常捕获事件,用来进行输出。同时跑一个任务不断执行垃圾回收

    public MainWindow()
{
InitializeComponent(); Dispatcher.UnhandledException += (sender, args) =>
{
args.Handled = true;
TextBlock.Text += $"Dispatcher UnhandledException {args.Exception.Message}\r\n";
}; TaskScheduler.UnobservedTaskException += (sender, args) =>
{
Dispatcher.InvokeAsync(() =>
{
TextBlock.Text += $"TaskScheduler UnobservedTaskException {args.Exception.InnerException!.Message}\r\n";
});
}; Task.Run(async () =>
{
while (true)
{
// 不断 GC 方便 Task 清理
await Task.Delay(TimeSpan.FromSeconds(1));
GC.Collect();
}
});
}

以上代码里面,因为 TaskScheduler 的 UnobservedTaskException 不是在主线程调度的,需要使用 Dispatcher 才能让内容输出在界面

接下来编写两个按钮的代码

    private void InvokeAsyncButton_OnClick(object sender, RoutedEventArgs e)
{
Dispatcher.InvokeAsync(() => throw new Exception($"在 Dispatcher.InvokeAsync 抛出异常"));
} private void BeginInvokeButton_OnClick(object sender, RoutedEventArgs e)
{
Dispatcher.BeginInvoke(new Action(() => throw new Exception($"在 Dispatcher.BeginInvoke 抛出异常")));
}

这里需要特别说明的是,咱是不应该抛出 Exception 类型的异常的,正确的做法是抛出特别类型的异常,例如 ArgumentException 等类型的异常。以上的代码仅用来进行测试行为

运行以上代码,分别点击两个按钮,可以看到有不同的输出,从而可以了解到这两个方法的异常处理行为

本文的代码放在githubgitee 欢迎访问

可以通过如下方式获取本文的源代码,先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码

git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin a7cbc4bd5e0ec41be5d0be719fa387adfb6bf52e

以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源。请在命令行继续输入以下代码

git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin a7cbc4bd5e0ec41be5d0be719fa387adfb6bf52e

获取代码之后,进入 GifellichelNurcikaifallhane 文件夹

WPF 使用 Dispatcher 的 InvokeAsync 和 BeginInvoke 的异常处理差别的更多相关文章

  1. Dispatcher中Invoke与BeginInvoke

    [同步]Invoke Application.Current.Dispatcher.Invoke(AutoIncreaseNumber); [异步]BeginInvoke Application.Cu ...

  2. WPF 线程 Dispatcher

    WPF 应用程序从两个线程开始: 一个用于处理呈现 一个用于管理 UI 呈现线程有效地隐藏在后台运行,而UI线程则接收输入.处理事件.绘制屏幕以及运行应用程序代码. 大多数应用程序都使用一个 UI 线 ...

  3. 【WPF】Dispatcher及线程操作

    WPF 应用程序启动后,会有两个线程: 1. 一个是用来处理UI呈现(处理UI的请求,比如输入和展现等操作). 2. 一个用来管理 UI的 (对UI元素及整个UI进行管理). 像Winform一样,W ...

  4. wpf 的dispatcher

    wpf项目中后台代码调用界面控件时,会提示进程调用的错误. private Thread JxThread = null;  //定义线程 private DataLoading.Loading nL ...

  5. WPF基础:Dispatcher介绍

    Disaptcher作用 不管是WinForm应用程序还是WPF应用程序,实际上都是一个进程,一个进程可以包含多个线程,其中有一个是主线程,其余的是子线程.在WPF或WinForm应用程序中,主线程负 ...

  6. WPF中Dispatcher未捕获异常之处理

    在UI线程中 在APP.XAML中定义 DispatcherUnhandledException事件 在工作线程中 PageMain.GetInstance().Dispatcher.Invoke(( ...

  7. 深入了解 WPF Dispatcher 的工作原理(Invoke/InvokeAsync 部分)

    深耕 WPF 开发的各位程序员大大们一定避不开使用 Dispatcher.跨线程访问 UI 当然免不了用到它,将某个任务延迟到当前任务之后执行也会用到它.Dispatcher.Invoke.Dispa ...

  8. WPF Dispatcher 一次小重构

    几个月之前因为项目需要,需要实现一个类似于WPF Dispatcher类的类,来实现一些线程的调度.之前因为一直做Asp.Net,根本没有钻到这个层次去,做的过程中,诸多不顺,重构了四五次,终于实现, ...

  9. 聊聊WPF中的Dispatcher

    DispatcherObject,Dispatcher,Thread之间的关系 我们都知道WPF中的控件类都是从System.Windows.Threading.DispatcherObject继承而 ...

  10. 调用线程无法访问此对象,因为另一个线程拥有该对象 [c# wpf定时器程序报的错误]

    WPF:Dispatcher.Invoke 方法,只有在其上创建 Dispatcher 的线程才可以直接访问DispatcherObject.若要从不同于在其上创建 DispatcherObject ...

随机推荐

  1. ARouter路由解析

    目录介绍 01.原生跳转实现 02.实现组件跳转方式 2.1 传统跳转方式 2.2 为何需要路由 03.ARouter配置与优势 04.跨进程组件通信 4.1 URLScheme 4.2 AIDL 4 ...

  2. linux查看域名对应的解析ip

    方法1-直接使用ping 方法2-nslookup 方法3-dig

  3. TP6框架--EasyAdmin学习笔记:定义路由

    这是我写的学习EasyAdmin的第二章,这一章我给大家分享下如何定义一条路由 正常的tp6定义路由方法如下: /route/admins/app.php 文件内容 //路由变量自定义 Route:: ...

  4. 记录--Vue常问问题整合

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 一.谈谈你对MVVM的理解? 映射关系简化,隐藏controller MVVM在MVC的基础上,把控制层隐藏掉了. Vue不是一个MVVM ...

  5. 把分钟数转化成几小时几分钟(100 -> 01:40)

    /// <summary> /// 把分钟数转化成几小时几分钟(100 -> 01:40) /// </summary> /// <param name=" ...

  6. C++简单实现vector

    向量 向量是序列容器,表示可以更改大小的数组. 就像数组一样,向量对其元素使用连续的存储位置,这意味着也可以使用指向其元素的常规指针上的偏移量来访问其元素,并且与数组一样高效.但与数组不同的是,它们的 ...

  7. #线性基#CF1100F Ivan and Burgers

    题目传送门 分析 线段树上直接维护线性基是三个log的.(一定要合并) 考虑一种分治做法,在 \([l,mid]\) 和 \((mid,r]\) 的询问分治处理, 跨过 \([mid,mid+1]\) ...

  8. Pyside2简单案例

    代码: from PySide2.QtWidgets import QApplication, QMainWindow, QPushButton, QPlainTextEdit app = QAppl ...

  9. PDF库 libharu 简单操作

    libharu官网:http://libharu.org/ 直接下载下来编译就可以使用了(*:我下载的版本是:libharu-libharu-v2.4.3-0-g8dbcfe4.tar)   一.编译 ...

  10. 使用GUI--tkinter 制作一个批量修改文件名的桌面软件

    ''' title:批量修改文件名称 author:huyang createtime:2021-01-29 14:50:00 ''' from tkinter import * from tkint ...