前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >.NET Core多线程 (3) 异步 - 下

.NET Core多线程 (3) 异步 - 下

作者头像
Edison Zhou
发布2023-08-11 16:57:12
1830
发布2023-08-11 16:57:12
举报
文章被收录于专栏:EdisonTalkEdisonTalk

深入分析使用Result方法死锁的原因

(1)慎用Result

  • 场景1:带有同步上下文的编程模型中有可能会出现死锁
    • 例如:WindowsForm、WPF
  • 场景2:同步+异步的场景中也有可能出现死锁
    • Result => 同步等待,它其实违背了异步编程的理念(初心)
    • 同步+异步混用会异常复杂,产生的Bug不易发现
      • 比如:在WindowsForm下,同步调用异步方法(task.GetResult())时,async的callback进入了Queue,而主线程需要不断地读取Queue的内容来执行,就容易造成死锁。
      • 为什么会出现死锁?
        • 主线程 要结束阻塞,必须要等待 延续Task 执行完毕
        • 延续Task 要执行完毕,必须要 主线程 从Queue中调取执行

(2).NET中的解决方案

方法一:不使用同步上下文(比如WindowsFormSynchronizationContext)

在自己的IO线程中完成,就没有所谓的Queue了。

代码语言:javascript
复制
var content = await client
  .GetStringAsync("http://cnblogs.com")
  .ConfigureAwait(false);

方法二:不阻塞主线程

即我们熟知的 async + await。

代码语言:javascript
复制
private async void button1_Click(object sender, EventArgs e)
{
    var content = await GetContent();

    textBox1.Text = content;
}

方法三:使用线程池完成

用线程池中的thread执行(比如:Task.Run),不用 main thread。

代码语言:javascript
复制
private void button1_Click(object sender, EventArgs e)
{
   var task = Task.Run(() =>
   {
       var content = GetContent().Result;

       return content;
   });

   textBox1.Text = task.Result;
}

(3)开源项目中的解决方案

比如Dapper这个开源项目中,它使用的是 task.ConfigureAwait(false) 的方式来避免死锁的。

代码语言:javascript
复制
private void button1_Click(object sender, EventArgs e)
{
   SqlConnection connection = new SqlConnection("Server=LocalHost; Persist Security Info=False;Integrated Security=SSPI;Database= PostDB;");

   var length = connection.ExecuteScalarAsync<int>("select count(1) from Post").Result;

   textBox1.Text = length.ToString();
}

常见的异步化编程模型

(1)异步延迟

Thread.Sleep方法的弊端:线程会休眠等待,等于浪费了资源。

Task.Delay方法的好处:避免了线程的等待,让线程被高效利用;其底层是Timer实现的(worker thread),通过Timer调度之后会切换线程。

代码语言:javascript
复制
await Task.Delay(1000 * 3);

(2)异步流

同步中的yield:不需要定义中间集合,可以延迟执行;

代码语言:javascript
复制
yield return urls;

异步中的yield:

代码语言:javascript
复制
foreach (var url in await urlsTask)
{
   if (url.Contains("csdn") || url.Contains("cnblogs"))
      yield return url;
}

Dapper项目中的案例:

代码语言:javascript
复制
while (await reader.ReadAsync())
{
   yield return reader;
}

(3)异步并发限制

方法1:借助异步锁实现:SemaphoreSlim.WaitAsync方法;

方法2:借助Task.WhenAll实现;

比如:限制最多两个task并行

代码语言:javascript
复制
foreach (var url in urls)
{
    tasks.Add(client.GetStringAsync(url));

    if (tasks.Count == 2)
    {
        var strlist = await Task.WhenAll(tasks);

        Console.WriteLine($"{DateTime.Now}, length1={strlist[0].Length}, length2={strlist[1].Length} tid={Environment.CurrentManagedThreadId}");

        tasks.Clear();
    }
}

异步和并行开发中的异常处理

(1)并行中的异常

问题1:Task的Wait和Result下的异常如何捕获?

Wait 针对无返回值,可以帮助捕获到;ExceptionResult 针对有返回值,可以帮助捕获到Exception;

问题2:为什么得到的是AggregateException异常?

因为,所谓的并行,肯定有多个Task,进而可能会抛多个Exception。而AggregateException相当于做了一个聚合,将所有Exception的Message组合在一起。

问题3:延续任务中的异常又该如何捕获?

比如,在延续task中发现了前面task有异常,怎么处理?

方式1:处理

代码语言:javascript
复制
if(t.IsFaulted)
{
    t.Exception.Handle(m => true);
}

方式2:不处理,往外抛

代码语言:javascript
复制
if(t.IsFaulted)
{
    t.Exception.Handle(m => false);
}

问题4:全局异常又该如何捕获?

在异步编程中可能会出现异常逃逸现象,如何全局发现那些被我们忽视的异常Task?

解法:借助Finalize线程,在回收托管资源时,调用析构函数。

示例:使用TaskScheduler.UnobservedTaskException 进行全局注册:

代码语言:javascript
复制
TaskScheduler.UnobservedTaskException += (sender, e) =>
{
   Console.WriteLine(e.Exception.Message);
   Console.WriteLine($"tid={Environment.CurrentManagedThreadId}");
};

GC.Collect(); // 仅用来测试

(2)异常中的异常

异常1:无await下的逃逸

因为,IO线程在抛异常时,控制流已经超出了try-catch块了。

异常2:在async avoid且有await下的逃逸

我们需要在async avoid方法中增加try-catch异常捕获机制。

关于异步的相关补充

关于async/await的大致流程图,一图胜千言:

关于IO完成端口(IOCP)的大致流程图,一图胜千言:

小结

本篇,我们复习了异步相关的基础知识,但由于内容太多,因此将其拆分为了两篇推文。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-08-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • (1)慎用Result
  • (2).NET中的解决方案
  • (3)开源项目中的解决方案
  • 常见的异步化编程模型
    • (1)异步延迟
      • (2)异步流
        • (3)异步并发限制
        • 异步和并行开发中的异常处理
          • (1)并行中的异常
            • (2)异常中的异常
            • 关于异步的相关补充
            • 小结
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档