合集:.NET Core多线程温故知新

去年换工作时系统复习了一下.NET Core多线程相关专题,学习了一线码农老哥的《.NET 5多线程编程实战》课程,我将复习的知识进行了总结形成本专题。

本篇,我们来继续复习一下异步的相关知识点,预计阅读时间10分钟。

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

(1)慎用Result

  • 场景1:带有同步上下文的编程模型中有可能会出现死锁

    • 例如:WindowsForm、WPF

  • 场景2:同步+异步的场景中也有可能出现死锁

    • Result => 同步等待,它其实违背了异步编程的理念(初心)

    • 同步+异步混用会异常复杂,产生的Bug不易发现

      • 比如:在WindowsForm下,同步调用异步方法(task.GetResult())时,async的callback进入了Queue,而主线程需要不断地读取Queue的内容来执行,就容易造成死锁。

      • 为什么会出现死锁?

        • 主线程 要结束阻塞,必须要等待 延续Task 执行完毕

        • 延续Task 要执行完毕,必须要 主线程 从Queue中调取执行

(2).NET中的解决方案

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

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

var content = await client
.GetStringAsync("http://cnblogs.com")
.ConfigureAwait(false);

方法二:不阻塞主线程

即我们熟知的 async + await。

private async void button1_Click(object sender, EventArgs e)
{
var content = await GetContent(); textBox1.Text = content;
}

方法三:使用线程池完成

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

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) 的方式来避免死锁的。

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调度之后会切换线程。

await Task.Delay(1000 * 3);

(2)异步流

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

yield return urls;

异步中的yield:

foreach (var url in await urlsTask)
{
if (url.Contains("csdn") || url.Contains("cnblogs"))
yield return url;
}

Dapper项目中的案例:

while (await reader.ReadAsync())
{
yield return reader;
}

(3)异步并发限制

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

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

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

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:处理

if(t.IsFaulted)
{
t.Exception.Handle(m => true);
}

方式2:不处理,往外抛

if(t.IsFaulted)
{
t.Exception.Handle(m => false);
}

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

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

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

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

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)的大致流程图,一图胜千言:

小结

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

下一篇,我们将复习一下锁机制的相关知识。

参考资料

一线码农,腾讯课堂《.NET 5多线程编程实战

不明作者,《Task调度与await》

作者:周旭龙

出处:https://edisonchou.cnblogs.com

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。

.NET Core多线程 (3) 异步 - 下的更多相关文章

  1. 从Nginx的Web请求处理机制中剖析多进程、多线程、异步IO

    Nginx服务器web请求处理机制 从设计架构来说,Nginx服务器是与众不同的.不同之处一方面体现在它的模块化设计,另一方面,也是最重要的一方面,体现在它对客户端请求的处理机制上. Web服务器和客 ...

  2. c# 中的多线程和异步

    前言: 1.异步和多线程有区别吗? 答案:多线程可以说是实现异步的一种方法方法,两者的共同目的:使主线程保持对用户操作的实时响应,如点击.拖拽.输入字符等.使主程序看起来实时都保持着等待用户响应的状态 ...

  3. Android Learning:多线程与异步消息处理机制

    在最近学习Android项目源码的过程中,遇到了很多多线程以及异步消息处理的机制.由于之前对这块的知识只是浅尝辄止,并没有系统的理解.但是工程中反复出现让我意识到这个知识的重要性.所以我整理出这篇博客 ...

  4. Android多线程及异步处理问题

    1.问题提出 1)为何需要多线程? 2)多线程如何实现? 3)多线程机制的核心是啥? 4)到底有多少种实现方式? 2.问题分析 1)究其为啥需要多线程的本质就是异步处理,直观一点说就是不要让用户感觉到 ...

  5. OS X 和iOS 中的多线程技术(下)

    OS X 和iOS 中的多线程技术(下) 上篇文章中介绍了 pthread 和 NSThread 两种多线程的方式,本文将继续介绍 GCD 和 NSOperation 这两种方式.. 1.GCD 1. ...

  6. 初步谈谈 C# 多线程、异步编程与并发服务器

    多线程与异步编程可以达到避免调用线程异步阻塞作用,但是两者还是有点不同. 多线程与异步编程的异同: 1.线程是cpu 调度资源和分配的基本单位,本质上是进程中的一段并发执行的代码. 2.线程编程的思维 ...

  7. Linux 多线程 - 线程异步与同步机制

    Linux 多线程 - 线程异步与同步机制 I. 同步机制 线程间的同步机制主要包括三个: 互斥锁:以排他的方式,防止共享资源被并发访问:互斥锁为二元变量, 状态为0-开锁.1-上锁;开锁必须由上锁的 ...

  8. C#基础之多线程与异步

    1.基本概念 多线程与异步是两个不同概念,之所以把这两个放在一起学习,是因为这两者虽然有区别,但也有一定联系. 多线程是一个技术概念,相对于单线程而言,多线程是多个单线程同时处理逻辑.例如,假如说一个 ...

  9. C#多线程与异步

    1.什么是异步同步 如果一个方法被调用,调用者需要等待该方法被执行完毕之后才能继续执行,则是同步. 如果方法被调用后立刻返回,即使该方法是一个耗时操作,也能立刻返回到调用者,调用者不需要等待该方法,则 ...

  10. async/await到底该怎么用?如何理解多线程与异步之间的关系?

    前言 如标题所诉,本文主要是解决是什么,怎么用的问题,然后会说明为什么这么用.因为我发现很多萌新都会对之类的问题产生疑惑,包括我最初的我,网络上的博客大多知识零散,刚开始看相关博文的时候,就这样.然后 ...

随机推荐

  1. 0x01 基础算法

    目录 基础算法 排序 快速排序 归并排序 二分 整数二分 浮点数二分 高精度 输入输出 高精度加法 高精度减法 高精度乘法 高精度除法 前缀和 差分 双指针算法 位运算 离散化 区间合并 基础算法 排 ...

  2. MCP开发应用,使用python部署sse模式

    一.概述 MCP服务端当前支持两种与客户端的数据通信方式:标准输入输出(stdio)  和 基于Http的服务器推送事件(http sse) 1.1 标准输入输出(stdio) 原理:  标准输入输出 ...

  3. 🎀文件对比工具--BeyondCompare

    简介 Beyond Compare 是一款功能强大的文件和文件夹比较工具,由Scooter Software开发.它可以帮助用户轻松地比较文件和文件夹的差异,并且可以合并变化.同步文件以及备份重要数据 ...

  4. python开发箱号批量查询关联SN号码的程序

    # 需要导入的包 import tkinter as tk from tkinter import ttk, messagebox, filedialog import pyodbc import p ...

  5. eolinker脚本代码[Javascript]:脚本(函数)之间传参案例

    场景描述: 有等级卡A.B,依次通过查询A.B,检查A.B下是否会会员,如果有,调整会员身份,没有着执行下一个循环 全部脚本:脚本中红色标注的user即脚本之间的传参 //以下代码为示例代码(支持原生 ...

  6. Robot Framework使用循环

    1.普通的For循环 在一个普通的For循环中,循环开始的关键字是 :FOR ,其中的:用于与一般关键字做区分,对于循环结 构体内的每一行,使用 \ 作为改行的行首关键字.对于循环中的变量,可以在 I ...

  7. win mysql实现主从同步(精简版)

    最近项目要弄读写分离,那首先要实现主从同步啊,网上教程很多,但大多都看得云里雾里,so,有了这个精简版: 主库my.ini添加配置: #数据库ID号, 为1时表示为Master,其中master_id ...

  8. grafana最新任意文件读取

    一.Grafana简介 Grafana是一个跨平台的开源的度量分析和可视化工具,可以通过将采集的数据查询然后可视化的展示,并及时通知.它主要有以下六大特点: 1.展示方式:快速灵活的客户端图表,面板插 ...

  9. SpringBoot文件上传--转载

    转载地址:https://www.jianshu.com/p/85017f5ecba1

  10. web框架实例

    s5.py from wsgiref.simple_server import make_server def handle_index():     return [b'<h1>Hell ...