首页 > 其他分享 >[转]WPF 使用 Dispatcher 的 InvokeAsync 和 BeginInvoke 的异常处理差别

[转]WPF 使用 Dispatcher 的 InvokeAsync 和 BeginInvoke 的异常处理差别

时间:2024-03-22 14:14:10浏览次数:30  
标签:git 抛出 BeginInvoke InvokeAsync WPF 异常 Dispatcher

一般认为 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 等类型的异常。以上的代码仅用来进行测试行为

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

本文的代码放在github 和 gitee 欢迎访问

可以通过如下方式获取本文的源代码,先创建一个空文件夹,接着使用命令行 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 文件夹

转自 https://www.cnblogs.com/lindexi/p/17679628.html

标签:git,抛出,BeginInvoke,InvokeAsync,WPF,异常,Dispatcher
From: https://www.cnblogs.com/siyunianhua/p/18089335

相关文章

  • WPF TextBlock根据值显示不同的内容或格式
    当TextBlock绑定的值IsChek=1时显示为“是”;IsCheck=2时显示为“否”,同时设置文字颜色为红色<TextBlockFontWeight="Bold"VerticalAlignment="Center"><TextBlock.Style><StyleTargetType="TextBlock"><Style.Tr......
  • 分享一个自己写的wpf的datagrid分页工具
    <StackPanelHorizontalAlignment="Center"><DataGridx:Name="datagrid1"AutoGenerateColumns="False"CanUserAddRows="False"Height="200"Margin="0,10">......
  • 【WPF应用4】WPF界面对象编辑
    简介WPF(WindowsPresentationFoundation)是.NET框架的一部分,它为开发人员提供了一个用于构建桌面应用程序用户界面的强大平台。WPF界面对象编辑是指在WPF应用程序中创建、设计和修改用户界面元素的过程。这些界面对象不仅包括基本的控件如按钮、文本框和标签,还包括更复杂的......
  • WPF如何给window加阴影效果
    <Stylex:Key="WindowStyle1"TargetType="{x:TypeWindow}"><SetterProperty="Template"><Setter.Value><ControlTemplateTargetType="{x:TypeWind......
  • WPF 添加系统托盘
    提问WPF如何添加系统托盘回答1.引入nugethandycontrolcsxmlns:hc="https://handyorg.github.io/handycontrol"2.窗体添加控件cs<hc:NotifyIconx:Name="NotifyIconContextContent"Text="软件名称"ContextMenu="{StaticResourceContextMenu}&qu......
  • WPF MVVM模式ListBox下ContextMenu绑定Command的方法以及将所选的Item的数据传回去
    需求:ListBoxItem上右键,将数据传参。疑问:ContextMenu不继承DataContext,导致直接用RelativeSource找根的方式也绑定不到。解决方法:在ListBox.ContextMenu里写菜单,就可以直接绑定到ViewModel层的命令了,参数先用RelativeSource找到ContextMenu,再绑定PlacementTarget.SelectedItem。......
  • 线程同步,命令行程序创建承载WPF程序运行的另一种实现
    WPFApplication类WPFApplication类是WPF应用程序的核心。它负责管理应用程序的生命周期、创建和管理应用程序的主窗口、处理应用程序事件以及提供对应用程序资源的访问。主要方法和属性WPFApplication类包含以下主要方法和属性:构造函数: 构造函数用于初始化应用程序......
  • 线程同步 SynchronizationContext 在命令行程序中创建一个ui线程承载WPF运行
    1、创建一个线程作为ui线程ui线程要求必须是STA线程vart=newThread(()=>{});t.SetApartmentState(ApartmentState.STA);t.Start();2、创建一个公共的SynchronizationContext作为同步的入口在.NET框架中,Dispatcher是一个类,它负......
  • WPF --- 触摸屏下的两个问题
    合集-桌面应用(8) 1.WPF---非Button自定义控件实现点击功能2023-08-172.MVVM---实现多层级通知2023-08-053.WPF---TextBox的输入校验2023-11-164.WPF---重写圆角DataGrid样式2023-11-175.WPF---如何重写WPF原生控件样式2023-11-176.WPF---如何以Binding方式隐......
  • WPF —— 控件模版和数据模版
    1:控件模版简介:自定义控件模版:自己添加的样式、标签,控件模版也是属于资源的一种,    每一个控件模版都有一唯一的key,在控件上通过template属性进行绑定什么场景下使用自定义控件模版,当项目里面多个地方使用到相同效果,这时候可以把相同    效果封装成一个......