一般认为 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. Adapter分组封装

    YCGroupAdapter 01.前沿说明 1.1 案例展示效果 1.2 该库功能和优势 1.3 相关类介绍说明 02.如何使用 2.1 如何引入 2.2 最简单使用 2.3 使用建议 03.常用a ...

  2. Cesium渲染模块之FBO与RBO

    1. 引言 Cesium是一款三维地球和地图可视化开源JavaScript库,使用WebGL来进行硬件加速图形,使用时不需要任何插件支持,基于Apache2.0许可的开源程序,可以免费用于商业和非商业 ...

  3. 记录--uniapp 使用原生子窗体进行视频聊天

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 现在 uniapp 开发的实时音视频聊天类的 APP 大部分都要在 nvue 页面上进行开发.虽然 nvue 与 vue 的区别不是很大, ...

  4. PowerDesigner操作要点

    一.PowerDesigner解决name和code同步问题 工具-常规选项-General  Options-Dialog-Name to Code mirroring√去掉 二.PowerDesi ...

  5. Elastic实战:彻底解决spring-data-elasticsearch日期、时间类型数据读取报错问题

    0. 引言在使用spring-data-elasticsearch读取es中时间类型的数据时出现了日期转换报错,不少初学者会在这里困惑很久,所以今天我们专门来解读该问题的几种解决方案. 1. 问题分析 ...

  6. FTP上传中文文件,内容乱码

    记录一下: spring boot 程序 ftp上传中文文件,内容乱码. 1.刚开始程序部署在Windows平台上测试,发现上传后的文件内容是乱码,查看文件编码格式是ANSI(Windows下文本文件 ...

  7. .NET Emit 入门教程:第六部分:IL 指令:2:详解 ILGenerator 辅助方法

    前言: 经过前面几大部分的学习,已经掌握了 Emit 的前因后果,今天来详细讲解 Emit 中 IL 的部分内容. 如前文所讲,通过 DynamicMethod(或 MethodBuilder)可获得 ...

  8. 鸿蒙HarmonyOS实战-ArkUI组件(Radio)

    一.Radio Radio单选框是一种表单元素,允许用户从一组选项中选择一个选项.它由一个圆圈和一个标签组成,用户只能选择其中一个选项.Radio单选框通常用于表单中,用于收集用户选择的信息,例如用户 ...

  9. 2 CSS基本选择器

    2 基本选择器 id选择器 id选择器使用"#"进行标识,后面紧跟id名,其基本语法格式为: #id名{属性1:属性值1;属性2:属性值2;属性3:属性值3;} 该语法中,id名即 ...

  10. #离线#洛谷 5358 [SDOI2019]快速查询

    题目传送门 分析 由于询问次数很多,只能离线去做,考虑全局的操作都可以打标记. 对于单点的操作,实际上的数为 \(x'=x*Mul+Add\) 由于这两个标记单独撤销比较困难, 不妨开一个数组直接维护 ...