当前位置: 首页 > article >正文

C# 异步Task异常处理和堆栈追踪显示

Task的问题

在C#中异步Task是一个很方便的语法,经常用在处理异步,例如需要下载等待等方法中,不用函数跳转,代码阅读性大大提高,深受大家喜欢。

但是有时候发现我们的异步函数可能出现了报错,但是异常又没有抛出,导致我们不知道,我们下面来根据代码来分析解决。

正常使用中的问题

我们通常这样使用:

async void BeginTest()
{
	Loger.Debug($"测试开始,{Environment.CurrentManagedThreadId}");
	await asynctest();
	Loger.Debug($"测试结束,{Environment.CurrentManagedThreadId}");
}
async Task asynctest()
{
    Loger.Debug($"异步开始,{Environment.CurrentManagedThreadId}");
	await Task.Delay(3000);
	Loger.Debug($"异步等待,{Environment.CurrentManagedThreadId}");
	int a = 0;
	int b = 1 / a; //这里会报错
	Loger.Debug("异步结束");
}

我们使用await没有任何问题,如果出错了会中止,并抛出异常。
在这里插入图片描述

但是如果我们把调用代码的await去掉,改成如下:

async void BeginTest()
{
	Loger.Debug($"测试开始,{Environment.CurrentManagedThreadId}");
	_= asynctest();
	Loger.Debug($"测试结束,{Environment.CurrentManagedThreadId}");
}

我们会发现没有任何报错,这样我们的逻辑出现为题就不会被发现。
在这里插入图片描述

解决办法1 - void

对于这种不需要返回值的Task我们可以定义成void类型。

async void BeginTest()
{
	Loger.Debug($"测试开始,{Environment.CurrentManagedThreadId}");
	asynctest();
	Loger.Debug($"测试结束,{Environment.CurrentManagedThreadId}");
}
async void asynctest()
{
    Loger.Debug($"异步开始,{Environment.CurrentManagedThreadId}");
	await Task.Delay(3000);
	Loger.Debug($"异步等待,{Environment.CurrentManagedThreadId}");
	int a = 0;
	int b = 1 / a; //这里会报错
	Loger.Debug("异步结束");
}

运行后也可以抛出错误。
在这里插入图片描述

解决办法2 - UnobservedTaskException

如果我们就是要使用 _= asynctest()的方式调用,不想中断程序。那么这样相当于没有对任务持续观察,所以捕获不到问题,我们可以添加这样的方法来捕获。

TaskScheduler.UnobservedTaskException += TaskSchedulerUnobservedTaskException;

private static void TaskSchedulerUnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e)
{
    e.SetObserved();
	Exception ex = e.Exception;
	Loger.Debug(ex.Message+ ",StackTrace:" + ex.StackTrace);
}

我们的调用函数可以这样,需要额外加一个gc。

async void BeginTest()
{

	    Loger.Debug($"测试开始,{Environment.CurrentManagedThreadId}");
	    _= asynctest();
	    Loger.Debug($"测试结束,{Environment.CurrentManagedThreadId}");
	    
	    await Task.Delay(5000);
	    Loger.Warn("gc");
	    System.GC.Collect();
	    //等待终结器处理
	    GC.WaitForPendingFinalizers();
    
}

在这里插入图片描述
当GC的时候,并且task没引用,这时候才会进入UnobservedTaskException 。 我们能看到报错的原因,但是发现没有堆栈追踪数据,StackTrace是null的。

解决办法3 - Forget

我们自定义一个观察任务的方法

public static class TaskExtensions
{
    /// <summary>
    /// Observes the task to avoid the UnobservedTaskException event to be raised.
    /// </summary>
    public static void Forget(this Task task)
    {
        // note: this code is inspired by a tweet from Ben Adams: https://twitter.com/ben_a_adams/status/1045060828700037125
        // Only care about tasks that may fault (not completed) or are faulted,
        // so fast-path for SuccessfullyCompleted and Canceled tasks.
        if (!task.IsCompleted || task.IsFaulted)
        {
            // use "_" (Discard operation) to remove the warning IDE0058: Because this call is not awaited, execution of the current method continues before the call is completed
            // https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/functional/discards?WT.mc_id=DT-MVP-5003978#a-standalone-discard
            _ = ForgetAwaited(task);
        }

        // Allocate the async/await state machine only when needed for performance reasons.
        // More info about the state machine: https://blogs.msdn.microsoft.com/seteplia/2017/11/30/dissecting-the-async-methods-in-c/?WT.mc_id=DT-MVP-5003978
        async static Task ForgetAwaited(Task task)
        {
            try
            {
                // No need to resume on the original SynchronizationContext, so use ConfigureAwait(false)
                await task.ConfigureAwait(false);
            }
            catch (Exception ex) 
            {
                // Nothing to do here
                Loger.Warn("??????????"+ex.Message+", stacktrace: "+ex.StackTrace);
            }
        }
    }
}

调用函数这样:

async void BeginTest()
{
	Loger.Debug($"测试开始,{Environment.CurrentManagedThreadId}");
	asynctest().Forget();
	Loger.Debug($"测试结束,{Environment.CurrentManagedThreadId}");
}

运行后结果:
在这里插入图片描述
这样程序不会被打断还有报错输出。

参考

https://discussions.unity.com/t/async-and-uncaught-exceptions/824272/14
https://www.youtube.com/watch?v=ZFWxSQ-KjUc


http://www.kler.cn/a/392575.html

相关文章:

  • 力扣104 : 二叉树最大深度
  • 第74期 | GPTSecurity周报
  • jenkins提交gitee后自动部署
  • Android 配置默认输入法
  • RoseTTAFold MSA_emb类解读
  • 计算机毕业设计必看必学35755flask旅游景区热度可视化平台原创定制程序,java、PHP、python、小程序、文案全套、毕设成品等
  • iOS 18.1,未公开的新功能
  • OpenStack讲解和实例
  • 2022年蓝桥杯JavaB组 省赛 题目解析(含AC_Code)
  • 【达梦数据库】MYSQL迁移到DM字符集转换问题-UTF8mb4|转UTF8(UTF8mb3)
  • Dubbo 3.x源码(25)—Dubbo服务引用源码(8)notify订阅服务通知更新
  • AI绘画经验(stable-diffusion)
  • 如何理解DDoS安全防护在企业安全防护中的作用
  • 力扣(LeetCode)611. 有效三角形的个数(Java)
  • adworld - stack2
  • 基于 Express+JWT + Vue 的前后端分离架构
  • 黄色校正电容102j100
  • 树莓派(Raspberry Pi)Pico 2 C_C++开发环境配置(Docker+SDK)
  • SpringBoot后端解决跨域问题
  • 【Jenkins实战】Windows安装服务启动失败
  • [HAOI2015] 树上染色(树形 DP)
  • 项目技术栈-解决方案-消息队列
  • T507 buildroot linux4.9之AP6275S wifi/bt 以太网开发调试
  • 小白docker入门简介
  • day60 图论章节刷题Part10(Floyd 算法、A * 算法)
  • linq语句在CAD c# 二次开发中的应用——快速筛选curve中polyline