「C#」异步编程玩法笔记-async、await

news2024/11/11 5:49:29

C#5.0的时候引入了async和await两个修饰符,成为异步编程的核心关键字。
async 是修饰符,表明方法含有异步操作,但并不是说整个方法是异步的。async修饰的方法会先同步执行到第一处await的地方而后开始异步。
await可以理解为一异步特有的“return”。即返回一个正在运行的异步过程。并且等待该异步过程结束后再继续向await的下一句运行。
例如下方法

private static void Main(string[] args)
{
    Console.WriteLine("Application Start");
    AsyncTask1();
    Console.WriteLine("Application End");
}

private static async Task AsyncTask1()
{
    Console.WriteLine("AsyncVoid1");
    Thread.Sleep(1000);
    Console.WriteLine("AsyncVoid1: befor await");
    await Task.Run(() =>
    {
        Console.WriteLine("AsyncVoid1: Task Runing");
        Thread.Sleep(2000);
    });
    Console.WriteLine("AsyncVoid1: after await");
}

运行输出如下:

Application Start
AsyncVoid1
AsyncVoid1: befor await
AsyncVoid1: Task Runing
Application End
AsyncVoid1: after await

异步返回类型

async是方法的修饰,其对应的方法的返回值有

  • void
  • Task
  • Task、
  • 任何具有可访问的 GetAwaiter 方法的类型(GetAwaiter 方法返回的对象必须实现 ystem.Runtime.CompilerServices.ICriticalNotifyCompletion 接口)。
  • IAsyncEnumerable(对于返回异步流的异步方法)。

常用的是前三个。暂且置整理前三者的区别与不同。

async void

async不建议修饰void方法,因为void 方法无法使用await。比如上面的例子中,AsyncTask1方法再调用时可以不加await,也可以加await来实现在调用程序结束前等待其执行结束。比如调用方式改成如下:

static void Main(string[] args)
{
     //控制台Main入口不能加async
      MainAsync();
}
//方法内有await时,方法必须被async修饰。
private static async void MainAsync()
{
    Console.WriteLine("Application Start");
    await AsyncTask1();
    Console.WriteLine("Application End");
}

运行结果如下:

Application Start
AsyncVoid1
AsyncVoid1: befor await
AsyncVoid1: Task Runing
AsyncVoid1: after await
Application End

如果将AsyncTask1的返回类型变成void,则调用方中就不能用await来强制同步了。

private static async void MainAsync()
{
    Console.WriteLine("Application Start");
    await AsyncTask1();//编译报错,“无法等待void”
    Console.WriteLine("Application End");
}

private static async void AsyncTask1()
{
    Console.WriteLine("AsyncVoid1");
    Thread.Sleep(1000);
    Console.WriteLine("AsyncVoid1: befor await");
    await Task.Run(() =>
    {
        Console.WriteLine("AsyncVoid1: Task Runing");
        Thread.Sleep(2000);
    });
    Console.WriteLine("AsyncVoid1: after await");
}

async可以修饰返回void的方法,是因为这种多用于事件,例如按钮点击事件等,这些情景就非常符合async void的使用场景。

public class NaiveButton
{
    public event EventHandler? Clicked;

    public void Click()
    {
        Console.WriteLine("Somebody has clicked a button. Let's raise the event...");
        Clicked?.Invoke(this, EventArgs.Empty);
        Console.WriteLine("All listeners are notified.");
    }
}

public class AsyncVoidExample
{
    public static Main()
    {
        var button = new NaiveButton();
        button.Clicked += OnButtonClickedAsync;
        Console.WriteLine("Before button.Click() is called...");
        button.Click();
        Console.WriteLine("After button.Click() is called...");
    }
    
    private static async void OnButtonClickedAsync(object? sender, EventArgs e)
    {
        Console.WriteLine("   Handler 2 is starting...");
        Task.Delay(100).Wait();
        Console.WriteLine("   Handler 2 is about to go async...");
        await Task.Delay(500);
        Console.WriteLine("   Handler 2 is done.");
    }
}

async Task

返回Task类型的。个人概括的说,就是“执行操作但不返回任何值的异步方法”。即await 等待的一个无返回值的Task。
例如前文已有例子:

private static async Task AsyncTask1()
{
    Console.WriteLine("AsyncVoid1");
    Thread.Sleep(1000);
    Console.WriteLine("AsyncVoid1: befor await");
    await Task.Run(() =>
    {
        Console.WriteLine("AsyncVoid1: Task Runing");
        Thread.Sleep(2000);
    });
    Console.WriteLine("AsyncVoid1: after await");
}

需要说明的是,这里说的是async修饰的Task返回类型,从外部调用上,她只需要返回Task类型的实例对象。但实际上行需要返回已经开始运行Task对象。在调用处不能通过Task.Start()来开始任务,await接一个未开始的任务也只能如等一个已死的人回心转意。
从设计上讲,async和await总是搭配使用的,await后接的必定是正在运行的Task。

async Task

返回类型为Task的异步方法,和返回类型为Task的方法是两回事。直接看代码:

private static async Task<int> AsyncFuncReturn1()
{
    await Task.Run(() => 
    { 
        Thread.Sleep(500); 
    });            
    return 1;
}
private static Task<int> AsyncFuncReturn2()
{
    return Task<int>.Run(() => 
    { 
        Thread.Sleep(500); 
        return 1; 
    });
}

后者只是将Task的实例化过程进行包装。前者具有两方面功能,一方提供Task实例的执行权力,另一方面说明异步方法的返回值类型。两者是独立的。即异步方法中的Task类型可以自定义,只需要确保方法的返回值类型正确即可。
但是,这两个方法再调用时却是一致的

private async static void Caller()
{
    //通过await 等待获取结果
    int s1 = await AsyncFuncReturn1();
    int s2 = await AsyncFuncReturn2();
    //通过Result(转同步)获取结果
    int s11 = AsyncFuncReturn1().Result;
    int s12 = AsyncFuncReturn2().Result;
    //先获取Task
    var t1 = AsyncFuncReturn1();
    var t2 = AsyncFuncReturn2();
    //在Task运行时先同步处理其他事情之后再获取结果
    int s111 = t1.Result;
    int s112 = t2.Result;
}

但优先考虑使用有async修饰的方式,因为这样,如果你没有写await,编译器通常会提醒你,以防止你返回的是一个没有开始的任务。

await 和 Task.Wait()/Task.Result

await 和Task.Wait()的作用都有等待任务结束的功能,不同的是,就想前面说的,await还有一层“return”的意思,或者说更像“yield”。即让await所在方法的被调用者可以继续下一步操作。(这里只是说“可以”,即对外产生这样的能力,而如果await所在方法返回的值是void类型,调用者就不能强制同步了)
依然用上面的示例:

private static void Main(string[] args)
{
    Console.WriteLine("Application Start");
    AsyncTask1();
    Console.WriteLine("Application End");
}

private static async Task AsyncTask1()
{
    Console.WriteLine("AsyncVoid1");
    Thread.Sleep(1000);
    Console.WriteLine("AsyncVoid1: befor await");
    await Task.Run(() =>
    {
        Console.WriteLine("AsyncVoid1: Task Runing");
        Thread.Sleep(2000);
    });
    Console.WriteLine("AsyncVoid1: after await");
}

运行输出如下:

Application Start
AsyncVoid1
AsyncVoid1: befor await
AsyncVoid1: Task Runing
Application End
AsyncVoid1: after await

可以看到,先输出了Application End,即AsyncTask1中的await等待异步任务时,主程序依然在运行。
一方面,如果将AsyncTask1改造成如下

private static void AsyncTask1_2()
{//没有await,不需要async修饰,也不会返回Task.
    Console.WriteLine("AsyncVoid1");
    Thread.Sleep(1000);
    Console.WriteLine("AsyncVoid1: befor await");
    Task.Run(() =>
    {
        Console.WriteLine("AsyncVoid1: Task Runing");
        Thread.Sleep(2000);
    }).Wait();
    Console.WriteLine("AsyncVoid1: after await");
}

这样调用AsyncTask1_2()方法将变成纯粹的同步任务。
另一方面,如果不改变AsyncTask1()方法,而改变调用方式:

private static void Main(string[] args)
{
    Console.WriteLine("Application Start");
    AsyncTask1().Wait();
    Console.WriteLine("Application End");
}

其也是将AsyncTask1()强制转换同步方法。
Task.Wait()和Task.Result在异步同步问题上是一致的,不同的是Result是针对Task任务的,先内部Wait()而后返回结果。

async与await的执行顺序

需要引用一下巨硬家的官方讲解,还是很清楚的

在这里插入图片描述
关系图中的数字对应于以下步骤,在调用方法调用异步方法时启动。

  1. 调用方法调用并等待 GetUrlContentLengthAsync 异步方法。
  2. GetUrlContentLengthAsync 可创建 HttpClient 实例并调用 GetStringAsync 异步方法以下载网站内容作为字符串。
  3. GetStringAsync 中发生了某种情况,该情况挂起了它的进程。 可能必须等待网站下载或一些其他阻止活动。 为避免阻止资源,GetStringAsync 会将控制权出让给其调用方 GetUrlContentLengthAsync。
  4. 由于尚未等待 getStringTask,因此,GetUrlContentLengthAsync 可以继续执行不依赖于 GetStringAsync 得出的最终结果的其他工作。 该任务由对同步方法 DoIndependentWork 的调用表示。
  5. DoIndependentWork 是完成其工作并返回其调用方的同步方法。
  6. GetUrlContentLengthAsync 已运行完毕,可以不受 getStringTask 的结果影响。 接下来,GetUrlContentLengthAsync 需要计算并返回已下载的字符串的长度,但该方法只有在获得字符串的情况下才能计算该值。
  7. GetStringAsync 完成并生成一个字符串结果。 字符串结果不是通过按你预期的方式调用 GetStringAsync 所返回的。 (记住,该方法已返回步骤 3 中的一个任务)。相反,字符串结果存储在表示 getStringTask 方法完成的任务中。 await 运算符从 getStringTask 中检索结果。 赋值语句将检索到的结果赋给 contents。
  8. 当 GetUrlContentLengthAsync 具有字符串结果时,该方法可以计算字符串长度。 然后,GetUrlContentLengthAsync 工作也将完成,并且等待事件处理程序可继续使用。 在此主题结尾处的完整示例中,可确认事件处理程序检索并打印长度结果的值。 如果你不熟悉异步编程,请花 1 分钟时间考虑同步行为和异步行为之间的差异。 当其工作完成时(第 5 步)会返回一个同步方法,但当其工作挂起时(第 3 步和第 6 步),异步方法会返回一个任务值。 在异步方法最终完成其工作时,任务会标记为已完成,而结果(如果有)将存储在任务中。

简单说下重点,就是await执行完后会回到方法调用方地方。

private static void Main(string[] args)
{
    Caller();
}
private static void Caller()
{
    Console.WriteLine("Caller:Call AsyncFunc");
    AsyncFunc();
    Console.WriteLine("Caller:Call Finished");
    Thread.Sleep(300);
    Console.WriteLine("Caller:sleep 300ms");
    Thread.Sleep(300);
    Console.WriteLine("Caller:sleep 300ms");
    Console.WriteLine("Caller:end");
}

private static async Task AsyncFunc()
{
    Console.WriteLine("AsyncFunc:befor await");
    await Task.Run(() => { Console.WriteLine("await task running"); Thread.Sleep(500); });
    Console.WriteLine("AsyncFunc:after await");
}
Caller:Call AsyncFunc
AsyncFunc:befor await
Caller:Call Finished
await task running
Caller:sleep 300ms
AsyncFunc:after await
Caller:sleep 300ms
Caller:end

即是说上面例子中的AsyncFunc()异步方法执行到await会返回到Caller()中,如果Caller()中对异步方法的调用方式是同步的(如上)则Caller自身线程继续执行后续任务。
这一点在界面化编程中较为重要,代码不正确顺序会在窗体程序中造成死锁或异常。

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

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

相关文章

使用Python制作内马尔的胜利之舞代码版

不知道大家最近有没有被球星内马尔所吸引&#xff0c;反正我是对他的胜利之舞上瘾了。今天&#xff0c;我以程序猿的视角将他的胜利之舞做成代码版的视频。话不多说&#xff0c;先看看最终效果图&#xff1a; 哈哈哈哈&#xff0c;是不是看着还不错的样子。 之前我做过类似的教…

小程序游戏 vs h5游戏,技术优势分别有哪些

从“跳一跳”到“羊了个羊”微信小游戏上线4年时间&#xff0c;除了涌现出不少火爆全网的小游戏之外&#xff0c;也有类似于“动物餐厅”、“口袋奇兵”等游戏得以在此孵化繁荣&#xff0c;凭借着微信强大的社交属性小游戏成为游戏厂商在桌面端、App 端、H5 端之外争夺的另一个…

微软宣布 S2C2F 已被 OpenSSF 采用

开源供应链安全对大多数 IT 领导者来说是个日益严峻的挑战&#xff0c;围绕确保开发人员在构建软件时如何使用和管理开源软件 (OSS) 依赖项的稳健策略至关重要。Microsoft 发布安全供应链消费框架 (S2C2F) 是一个以消费为中心的框架&#xff0c;它使用基于威胁的风险降低方法来…

linux下安装部署es-head插件

es通过程序代码调用es 各种api接口。 es-head查看与显示es状态信息&#xff0c;数据量&#xff0c;具体数据。 1、elasticsearch-head介绍 官方地址: https://github.com/mobz/elasticsearch-head elasticsearch-head 是一款用来管理Elasticsearch集群的第三方插件工具。 e…

用二元泊松模型预测2022年世界杯淘汰赛结果

用二元泊松模型预测2022年世界杯淘汰赛结果 网上有很多文章用双泊松&#xff08;Double Poisson&#xff09;模型来预测世界杯比赛结果。但是双泊松模型有一个严重的缺陷&#xff0c;那就是它假设比赛中两队的比分是条件独立的。而我们都知道&#xff0c;在对抗性比赛中&…

国产API管理神器Eolink也太强了吧

一、研发痛点 什么是API研发管理 API研发管理是包含了API开发管理、开发团队协作、自动化测试、网关以及监控等等API管理全生命周期的一系列管理过程。可以帮助公司实现开发运维一体化&#xff0c;提升开发速度&#xff0c;达到降本增效的目标。 前端痛点 针对前端开发在使…

Metabase学习教程:权限-2

使用集合权限 设置具有权限的集合&#xff0c;以帮助用户组织和共享与其相关的工作。 集合保持问题,仪表板&#xff0c;和模型有条理&#xff0c;容易找到。将集合视为存储我们工作的文件夹是很有帮助的。集合权限授予一群人访问&#xff1a; 查看或编辑保存在集合中的问题、…

激活企业数字化采购价值,智慧采购管理系统助力半导体行业提升采购协同效率

如今&#xff0c;随着国内经济不断发展以及国家对半导体行业的政策扶持&#xff0c;我国半导体行业发展迅速&#xff0c;半导体技术含量与日俱增的同时&#xff0c;也对我国半导体企业的管理效能与管理工具提出了更高的要求。在海外对国内半导体产业发展日益严格的当下&#xf…

Netty篇之如何优雅的关服

强制关服的危害 linux中关服如果我们使用 kill -9 pid号或者在windows中使用 taskkill /f /pid pid号来关服的话&#xff0c;相当于是突然断电的方式&#xff0c;会导致如下几种情况。 缓存中的数据丢失正在进行文件的写操作&#xff0c;没有更新完成&#xff0c;突然退出会…

技术分享 | Redis 集群架构解析

作者&#xff1a;贲绍华 爱可生研发中心工程师&#xff0c;负责项目的需求与维护工作。其他身份&#xff1a;柯基铲屎官。 本文来源&#xff1a;原创投稿 *爱可生开源社区出品&#xff0c;原创内容未经授权不得随意使用&#xff0c;转载请联系小编并注明来源。 一、集群架构的一…

决策树算法中处理噪音点

目录 如何解决&#xff1f;——采用剪枝的方法。 预剪枝 后剪枝 如果训练集中存在噪音点&#xff0c;模型在学习的过程总会将噪音与标签的关系也学习进去&#xff0c;这样就会造成模型的过拟合化&#xff0c;也就是模型在训练集的分类效果很好&#xff0c;在未知数据上处理效…

python快速实现2048小游戏

《2048》是一款比较流行的数字游戏&#xff0c;最早于2014年3月20日发行。原版2048首先在GitHub上发布&#xff0c;原作者是Gabriele Cirulli&#xff0c;后被移植到各个平台。这款游戏是基于《1024》和《小3传奇》的玩法开发而成的新型数字游戏。 操作指南&#xff1a; 每次…

景区票务系统毕业设计,景区售票系统设计与实现,旅游售票系统毕业设计源码分析

项目背景和意义 目的&#xff1a;本课题主要目标是设计并能够实现一个基于java的景区景点预约购票系统&#xff0c;整体使用javaMySql的B/S架构&#xff0c;技术上采用了springboot框架&#xff1b;通过后台添加景区资讯、景点介绍&#xff0c;管理用户订单&#xff1b;用户通过…

Vue怎么通过JSX动态渲染组件

一、明确需求 有一组数组结构如下&#xff1a; const arr [ { tag: van-field }, // 输入框{ tag: van-cell }, // 弹出层{ tag: van-stepper } // 步进器 ] 想通过循环arr&#xff0c;拿到tag渲染对应的组件。 下面我们分析如何写才是最优。 二、进行分析 2.1 v-if走天…

JavaScript期末大作业:基于HTML+CSS+JavaScript黑色的bootstrap响应式企业博客介绍模板

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

JTable详细介绍

目录 一、基本表格 二、修改列宽并显示列的名称 三、使用AbstractTableModel抽象类存储数据 一、基本表格 显示一个Table需要两组数据 1. 一维数组&#xff1a; String[]columnNames 表示表格的标题 2. 二维数组&#xff1a; String[][] heros 表格中的内容 默认情况下&…

使用openssl工具生成CSR文件

使用OpenSSL工具生成CSR文件 登录服务器。 安装OpenSSL工具。 执行以下命令&#xff0c;生成CSR文件。 openssl req -new -nodes -sha256 -newkey rsa:2048 -keyout [$Key_File] -out [$OpenSSL_CSR] 说明 -new&#xff1a;指定生成一个新的CSR文件。 -nodes&#xff1a;指定…

WindowsNT下的OpenGL

三、WindowsNT下的OpenGL 3.1、Windows NT下的OpenGL函数   如前面的章节所述&#xff0c;Windows NT下的OpenGL同样包含100多个库函数&#xff0c;这些函数都按一定的格式来命名&#xff0c;即每个函数都以gl开头。Windows NT下的OpenGL除了具有基本的OpenGL函数外&#xf…

威马汽车欲曲线上市:沈晖已提前持股并任职,销量垫底、员工降薪

12月5日&#xff0c;港交所上市公司Apollo出行&#xff08;HK:00860&#xff09;发布公告称&#xff0c;该公司拟收购一家从事智能电动车的公司&#xff0c;目标公司的业务涵盖一系列配备先进技术的智能电动车&#xff0c;目标客户为中国年轻且精通技术的用户&#xff08;特别是…

小迪-day14(注入类型之提交注入)

1、参数提交注入 1.1 明确参数类型 数字&#xff0c;字符&#xff0c;搜索&#xff0c;JSON等1.2 明确提交方式 GET, POST,COOKIE,REQUEST&#xff0c;HTTP头等可能有些网站是以Request的方式接受参数&#xff0c;所以GET和POST都行 注入的地方可能在User-Agent、cookie上&a…