WPF 使用 Dispatcher 的 InvokeAsync 和 BeginInvoke 的异常处理差别
一般认为 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 等类型的异常。以上的代码仅用来进行测试行为
运行以上代码,分别点击两个按钮,可以看到有不同的输出,从而可以了解到这两个方法的异常处理行为
可以通过如下方式获取本文的源代码,先创建一个空文件夹,接着使用命令行 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 的异常处理差别的更多相关文章
- Dispatcher中Invoke与BeginInvoke
[同步]Invoke Application.Current.Dispatcher.Invoke(AutoIncreaseNumber); [异步]BeginInvoke Application.Cu ...
- WPF 线程 Dispatcher
WPF 应用程序从两个线程开始: 一个用于处理呈现 一个用于管理 UI 呈现线程有效地隐藏在后台运行,而UI线程则接收输入.处理事件.绘制屏幕以及运行应用程序代码. 大多数应用程序都使用一个 UI 线 ...
- 【WPF】Dispatcher及线程操作
WPF 应用程序启动后,会有两个线程: 1. 一个是用来处理UI呈现(处理UI的请求,比如输入和展现等操作). 2. 一个用来管理 UI的 (对UI元素及整个UI进行管理). 像Winform一样,W ...
- wpf 的dispatcher
wpf项目中后台代码调用界面控件时,会提示进程调用的错误. private Thread JxThread = null; //定义线程 private DataLoading.Loading nL ...
- WPF基础:Dispatcher介绍
Disaptcher作用 不管是WinForm应用程序还是WPF应用程序,实际上都是一个进程,一个进程可以包含多个线程,其中有一个是主线程,其余的是子线程.在WPF或WinForm应用程序中,主线程负 ...
- WPF中Dispatcher未捕获异常之处理
在UI线程中 在APP.XAML中定义 DispatcherUnhandledException事件 在工作线程中 PageMain.GetInstance().Dispatcher.Invoke(( ...
- 深入了解 WPF Dispatcher 的工作原理(Invoke/InvokeAsync 部分)
深耕 WPF 开发的各位程序员大大们一定避不开使用 Dispatcher.跨线程访问 UI 当然免不了用到它,将某个任务延迟到当前任务之后执行也会用到它.Dispatcher.Invoke.Dispa ...
- WPF Dispatcher 一次小重构
几个月之前因为项目需要,需要实现一个类似于WPF Dispatcher类的类,来实现一些线程的调度.之前因为一直做Asp.Net,根本没有钻到这个层次去,做的过程中,诸多不顺,重构了四五次,终于实现, ...
- 聊聊WPF中的Dispatcher
DispatcherObject,Dispatcher,Thread之间的关系 我们都知道WPF中的控件类都是从System.Windows.Threading.DispatcherObject继承而 ...
- 调用线程无法访问此对象,因为另一个线程拥有该对象 [c# wpf定时器程序报的错误]
WPF:Dispatcher.Invoke 方法,只有在其上创建 Dispatcher 的线程才可以直接访问DispatcherObject.若要从不同于在其上创建 DispatcherObject ...
随机推荐
- C# 12 拦截器 Interceptors
拦截器Interceptors是一种可以在编译时以声明方式替换原有应用的方法. 这种替换是通过让Interceptors声明它拦截的调用的源位置来实现的. 您可以使用拦截器作为源生成器的一部分进行修改 ...
- npm install安装依赖包时报错npm ERR! command C:\Windows\system32\cmd.exe /d /s /c node install.js,npm ERR! ChromeDriver installation failed Error with http(s) request: Error: read ECONNRESET
PS E:\20231213\uirecorder> PS E:\20231213\uirecorder> PS E:\20231213\uirecorder> PS E:\2023 ...
- 记录--手写vm.$mount方法
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 一.概述 在我们开发中,经常要用到Vue.extend创建出Vue的子类来构造函数,通过new 得到子类的实例,然后通过$mount挂载到 ...
- 详解SSL证书系列(6)了解HTTP及网络基础
使用HTTP协议访问Web 你知道当我们在网页浏览器(比如Chrome)的地址栏中输入URL时,Web网页是如何呈现的吗? Web页面当然不会凭空显示出来.根据Web浏览器地址栏中指定的URL,W ...
- KingbaseESV8R6使用pageinspect插件观察空值
前言 在KingbaseES元组头数据中,有一个t_bits数组,用于存储空值位图.当元组中没有null值的时候,t_bits是空的,当元组有null值的列时,t_bits使用一个bit来表示列是否为 ...
- #矩阵乘法#洛谷 5343 【XR-1】分块
题目 分析 考虑dp,\(dp[i]=\sum dp[i-j]\) 既然\(j\)很小,那么这显然可以用矩阵乘法优化 代码 #include <cstdio> #include <c ...
- 2024最新AIGC系统ChatGPT网站源码,GPTs应用,Ai绘画网站源码
一.前言 SparkAi创作系统是基于ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统,支持OpenAI-GPT全模型+国内AI全模型.本期针对源码系统整体测试下来非常完美,那么 ...
- Windows开发的瑞士军刀,NewSPYLite发布
宗旨:延续了SpyLite的方便易用,同时也丰富了更多的功能,更适合开发者使用. 在SpyLite的基础功能上,开发了更多人性化并且好用的功能,增强的信息的可读性,以及软件总体的可用性. New Sp ...
- 从入门到精通:C++ 学习路线指南,附详细学习计划
C++是一种高级编程语言,广泛用于开发操作系统.应用程序.游戏和各种工具.如果你想学习这门语言,以下是一个适合初学者的学习路线: 第一步:学习C++基础知识 在学习C++之前,你需要掌握一些基础知识, ...
- C# DevExpress下GridControl控件的增删查改
DevExpress的GridControl控件可以从任何数据源绑定数据并进行增删查改等操作,和VS自带的dataGridView控件对比,GridControl控件可以实现更多自定义的功能,界面UI ...