【C#】async和await 续

news2024/9/28 7:22:57

前言

在文章《async和await》中,我们观察到了一下客观的规律,但是没有讲到本质,而且还遗留了一个问题:

这篇文章中,我们继续看看这个问题如何解决!

问题遗留

我们再看看之前写的代码:

static public void TestWait2()
{
     var t = Task.Factory.StartNew(async () =>
     {
         Console.WriteLine("Start");
         await Task.Delay(3000);
         Console.WriteLine("Done");
     });
     t.Wait();
     Console.WriteLine("All done");
 }

static public void TestWait3()
{
     var t = Task.Run(async () =>
     {
         Console.WriteLine("Start");
         await Task.Delay(3000);
         Console.WriteLine("Done");
     });
     t.Wait();
     Console.WriteLine("All done");
}

当时问题是,为啥 Task.Factory.StartNew 可以看到异步效果,而Task.Run中却是同步效果。
那其实是因为,Task.Factory.StartNew 返回的 t.Wait(); 它没卡住主线程,而Task.Run的 t.Wait();它卡住了。

那为啥,Task.Factory.StartNew没卡住呢?
这是应为 Task.Factory.StartNew 返回的变量 t 他是Task< Task >类型!

如果,Task.Run 返回的是Task类型,如果我们改成Task.Factory.StartNew,那么它 返回的类型就是Task<Task< int >>

在.Net4.0中提供一个Unwrap方法,用于将Task<Task< int>>解为Task< int>类型,所以如果代码改为:

static public async void Factory嵌套死等()
{
    Console.WriteLine($"Factory不嵌套死等{getID()}");
    var t = Task.Factory.StartNew(async() =>
    {
        Console.WriteLine($"Start{getID()}");
        await Task.Delay (1000);
        Console.WriteLine($"Done{getID()}");
    }).Unwrap();
    t.Wait();
    Console.WriteLine($"All done{getID()}");
}

那么此时 t.Wait(); 也能卡死主线程。

其实Task.Run(.net4.5引入) 是在 Task.Factory.StartNew(.net4.0引入) 之后出现的,Task.Run是为了简化Task.Factory.StartNew的使用。

t.Wait() 和 await t;

现在我从另一个角度分析问题。
使用 Task.Run,能不能达到异步的效果? 答案是肯定的!
不过,我们此时不应该使用 t.Wait(); 而是应该是 await t;

static public async void Run嵌套Await()
{
    Console.WriteLine($"Run嵌套Await{getID()}");
    var t = Task.Run(async () =>
    {
        Console.WriteLine($"Start{getID()}");
        await Task.Delay(1000);
        Console.WriteLine($"Done{getID()}");
    });
    await t;
    Console.WriteLine($"All done{getID()}");
}

在这里插入图片描述

这样的话就实现了异步效果。

await 是如何实现异步的

这里我们可以进一步分析一下。
“1” 是主线程的ID “5” 是 task 启的子线程 ID。
我发现All done 在 Done 后面执行的,这是应为 await t; 把主线程"遣返了"
而await t; 之后的代码(也就是All done 这句话的打印)是由子线程5接着完成的。

整个流程是这样的,当编译时,编译器看到了函数使用了 async 关键字,那么整个函数将被转换为一个带有状态机的函数,反编译后发现函数名称变为MoveNext。

当主线程执行到子函数时,遇到 await 那么此时 主线程就会返回(跳出整个子函数,去执行下一个函数),MoveNext呢就会切换状态机(由于状态机已经切换,下次MoveNext在被调用时,就会从await 处向下执行)。
不过,从现象看await 之后的代码,不是主线程调用了,而是Task的子线程。子线程会再次调用MoveNext,并且进入一个新的状态机。
这里就有一个结论,当主线程进入一个子函数,遇到await机会从函数直接返回,函数中以下的代码交给新的子线程执行。
为了证明一这一点,我又写了一个程序:

static public async Task AsyncInvoke()
{
    await Task.Run(() =>
    {
        Console.WriteLine($"This is 1 ManagedThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
        Thread.Sleep(1000);
    });
    Console.WriteLine($"1{getID()}");
    await Task.Run(() =>
    {
        Console.WriteLine($"This is 2 ManagedThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
        Thread.Sleep(1000);
    });
    Console.WriteLine($"2{getID()}");
    await Task.Run(() =>
    {
        Console.WriteLine($"This is 3 ManagedThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
        Thread.Sleep(1000);
    });
    Console.WriteLine($"3{getID()}");
    await Task.Run(() =>
    {
        Console.WriteLine($"This is 4 ManagedThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
        Thread.Sleep(1000);
    });
    Console.WriteLine($"4{getID()}");
}

执行效果如下:
在这里插入图片描述
你会发现不过一个函数里面有多少个await ,主线程遇到一个await就返回了,就跳出这个函数去执行其他的函数了。
函数剩下的await 后面的都是由子线程完成的!多个await 只是多个几个状态机而已。
所以在一个函数中,如果有个多个await ,除了第一个后面的都和主线程无关。

这里又出现了一个新的问题,为啥后面的线程ID都是5?这个其实不一定的,我重新跑了一次:
在这里插入图片描述
这次发现出现了两个子线程号 3 和 5,这是应为 Task 背后有个 线程池。Task 被翻译为任务,单纯的线程是指的Thread
Task 启动后,使用哪个线程是由背后的线程池提供,而这个线程池是由.net进行维护。包括回调什么时候发生都是由线程池中的一个线程通知Task对象!await 操作符 其实是 调用 Task对象的 ContinueWith,所以上面这段代码也可以这么写:

/// <summary>
/// 回调式写法
/// </summary>
public void TaskInvokeContinue()
{
    Task.Run(() =>
    {
        Console.WriteLine($"This is 1 ManagedThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
        Thread.Sleep(1000);
    }).ContinueWith(t =>
    {
        Console.WriteLine($"This is 2 ManagedThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
        Thread.Sleep(1000);
    }).ContinueWith(t =>
    {
        Console.WriteLine($"This is 3 ManagedThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
        Thread.Sleep(1000);
    }).ContinueWith(t =>
    {
        Console.WriteLine($"This is 4 ManagedThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
        Thread.Sleep(1000);
    })
    ;
    //不太爽---nodejs---回调式写法,嵌套是要疯掉
}

这就进一步体现了 await 用同步的方式,写异步的代码。
能实现这个的原因就是,函数已经被改造成一个状态机了。

到这里,我就把上次坑给填上了!下次我们在一起掰扯掰扯Task的一些有意思的用法。

小结

我觉得最重要的一点就是:
主线程遇到一个await就返回了,如何理解这个返回?
返回就是跳出这个函数,和这个函数没有半毛钱关系了,去执行其他下面的函数了。
该函数剩下的await后面的部分 都是由线程池中的子线程完成的!
理解这一点,有助于我们对异步代码的编写!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/804938.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

VR党建主题数字互动虚拟展馆软件开启党建铸魂育人新篇章

当今时代新媒体技术的发展对大学生的学习、生活等产生着深远的影响。高校作为党建育人的重要场所&#xff0c;充分借助VR技术的强大优势&#xff0c;合理运用到育人工作中&#xff0c;能够不断丰富教育内容。VR智慧党建展厅展馆结合VR技术营造的虚拟现实空间&#xff0c;将党的…

layui手机端上传文件时返回404 Not Found的解决方案(client_body_temp权限设置)

关于 1.client_body_temp的作用 client_body_temp是一个指令指定保存客户端请求体临时文件的目录路径&#xff0c;以及是否进行缓存的配置指令。 在Web服务器中&#xff0c;当客户端向服务器发送请求时&#xff0c;请求体中包含了请求的主体部分&#xff0c;比如表单数据、上…

基于flask徐州市天气信息可视化分析系统【纯干货分享,附源码04600】

摘 要 信息化社会内需要与之针对性的信息获取途径&#xff0c;但是途径的扩展基本上为人们所努力的方向&#xff0c;由于站在的角度存在偏差&#xff0c;人们经常能够获得不同类型信息&#xff0c;这也是技术最为难以攻克的课题。针对天气信息等问题&#xff0c;对天气信息进行…

【网络通信】一文读懂TCP/IP基础,IP、端口、网关、web、超媒体

TCP/IP贯穿在我们学习计算机的生涯中&#xff0c;但TCP/IP终究是个什么玩意儿&#xff1f;在了解他之前我们先看一段历史&#xff1a; 在网络发展初期&#xff0c;许多研究机构、计算机厂商和公司都开始大力发展计算机网络。自ARPANET出现之后&#xff0c;许多商品化的网络系统…

实现二叉排序树

一&#xff1a;二叉树和二叉搜索树 二叉树中的节点最多只能有两个子节点&#xff1a;一个是左侧子节点&#xff0c;另一个是右侧子节点。这个定义有助于我们写出更高效地在树中插入、查找和删除节点的算法&#xff0c;二叉树在计算机科学中的应用非常广泛。 **二叉搜索树&…

Consul的容器服务更新与发现

Consul的容器服务更新与发现 一&#xff1a;Docker consul的容器服务更新与发现&#xff08;1&#xff09;什么是服务注册与发现&#xff08;2&#xff09;什么是consulconsul提供的一些关键特性&#xff1a; 二&#xff1a;consul 部署consul服务器1. 建立 Consul 服务2. 查看…

一文带你全面理解向量数据库

近些年来&#xff0c;向量数据库引起业界的广泛关注&#xff0c;一个相关事实是许多向量数据库初创公司在短期内就筹集到数百万美元的资金。 你很可能已经听说过向量数据库&#xff0c;但也许直到现在才真正关心向量数据库——至少&#xff0c;我想这就是你现在阅读本文的原因…

美国过境签证的办理流程和注意事项

美国过境签证是一种临时签证&#xff0c;允许你在前往其他国家的途中经过美国。这对于很多旅行者来说是一个便捷的选择&#xff0c;但在申请和办理过程中也需要注意一些要点。 首先&#xff0c;申请美国过境签证需要前往美国驻中国大使馆或领事馆递交申请。以下是办理美国过境签…

四磺酸酞菁锌,61586-86-5,Zn(II) Phthalocyanine tetrasulfonic acid,广泛用于染色

资料编辑|陕西新研博美生物科技有限公司小编MISSwu​ 四磺酸酞菁锌试剂 | 基础知识概述&#xff08;部分&#xff09;: 中文名称&#xff1a;四磺酸酞菁锌 英文名称&#xff1a;Zn(II) Phthalocyanine tetrasulfonic acid CAS号&#xff1a;61586-86-5 分子式&#xff1a;C32H…

图数据库实践 - 如何将图数据库应用于网络安全

网络化办公为企业创造便捷的同时&#xff0c;也带来了数据泄露的威胁。根据IBM Security最新发布的年度《数据泄露成本报告》显示&#xff0c;2023全球数据泄露的平均成本达到445万美元&#xff0c;创该报告有史以来以来最高记录&#xff0c;也较过去3年均值增长了15%。同一时期…

2.3 HLSL常用函数

一、函数介绍 函数图像参考网站&#xff1a;Graphtoy 1.基本数学运算 函数 含义 示例图 min(a,b) 返回a、b中较小的数值 mul(a,b) 两数相乘用于矩阵计算 max(a,b) 返回a、b中较大的数值 abs(a) 返回a的绝对值 round(x) 返回与x最近的整数 sqrt(x) 返回x的…

QT【day4】

chat_QT服务器端&#xff1a; //.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include<QTcpServer> //服务器类 #include<QTcpSocket> //客户端类 #include<QMessageBox> //对话框类 #include<QList> //链表容器 #inc…

美团/华为/字节/滴滴等大厂真实面试面经

一、美团测试开发面经 一面&#xff0c;1小时 自我介绍 自已觉得最好的项目&#xff1f;主要做了什么&#xff1f;遇到的最大困难&#xff1f; 浏览器输入网址后发生了什么&#xff1f; 三次握手和四次挥手 http和https的区别 https的加密过程 知道哪些排序算法 快排的…

现在入行软测=49年入国军?三句话,让面试官再掏2K

还有一个月就步入金九银十&#xff0c;很多软测人吐槽因为疫情&#xff0c;公司都在裁员&#xff0c;别说跳槽涨薪&#xff0c;能保住现在的工作就不错了。 但也有那么一批人&#xff0c;凭借自己口才与实力拿到年薪近50W的offer。面试是初见1小时就要相互了解优缺点的过程&am…

工作10年的老码农手把手教你如何3分钟看懂IT技术管理!速收藏!

老陈是谁&#xff1f; 一个码龄十年的老码农&#xff0c;从刚毕业开始被代码折磨的死去活来&#xff0c;到公司里“被迫”成为多线技术栈的“工程师”&#xff0c;这几年又从IT技术转向做IT管理。 基本可以说从一个坑跳到了另一个坑&#xff0c;虽然坑多水深&#xff0c;但是…

【Golang】Golang进阶系列教程--Golang中文件目录操作的实现

文章目录 一、文件二、文件目录三、文件目录操作3.1、读取文件3.1.1、方法一 (file.Read())3.1.2、方法二 (bufio读取文件)3.1.3、方法三 (ioutil 读取方法) 3.2、写入文件3.2.1、方法一3.2.2、方法二3.2.3、方法三 (ioutil写入文件) 3.3、复制文件3.3.1、方法一3.3.2、方法二 …

AI人工智能未来在哪里?2023年新兴产业人工智能有哪些就业前景?

AI人工智能未来在哪里&#xff1f;2023年新兴产业人工智能有哪些就业前景&#xff1f; 随着科技的不断发展&#xff0c;人工智能技术也在不断地进步。在数字化时代&#xff0c;人工智能技术已经渗透到了我们生活的各个方面。2023年为止中国产业80%已经实现半自动化&#xff0c;…

element 级联 父传子

html代码例子 父组件 <el-cascaderstyle"width: 100%"change"unitIdChange":options"unitOptions"filterablev-model"formInline.unitId":props"unitProps"/></el-form-item>//改变级联传值到这个组件里面<r…

自动化测试如何做?搭建接口自动化框架从0到1实战(超细)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 传统软件测试行业…

C#时间轴曲线图形编辑器开发1-基本功能

目录 一、前言 1、简介 2、开发过程 3、工程下载链接 二、基本功能实现 1、绘图面板创建 &#xff08;1&#xff09;界面布置 &#xff08;2&#xff09;显示面板代码 &#xff08;3&#xff09; 面板水平方向、竖直方向移动功能实现 &#xff08;4&#xff09;面板放…