C#使用 Async 和 Await 的异步编程

news2025/1/11 14:49:34

总目录


文章目录

  • 总目录
  • 前言
  • 一、概述
  • 二、命名规范
  • 三、await/async的作用
  • 四、基本使用
  • 五、使用Async和Await实现多任务顺序执行且不阻塞
    • 1.同步执行
    • 2.并行执行
    • 3.并行且可指定顺序执行
  • 总结


前言

C# 中的 Async 和 Await 关键字是异步编程的核心。 通过这两个关键字,可以使用 .NET Framework、.NET Core 或 Windows 运行时中的资源,轻松创建异步方法(几乎与创建同步方法一样轻松)。


一、概述

  • 使用 async 修饰符可将方法、lambda 表达式或匿名方法指定为异步。 如果对方法或表达式使用此修饰符,则其称为异步方法。
  • async 关键字修饰的方法一般包含一个或多个await 表达式或语句,如果不包含 await 表达式或语句,则该方法将同步执行。 编译器警告将通知你不包含 await 语句的任何异步方法。
  • async 关键字是上下文关键字,原因在于只有当它修饰方法、lambda 表达式或匿名方法时,它才是关键字。 在所有其他上下文中,都会将其解释为标识符。
  • 只能在通过 async 关键字修饰的方法、lambda 表达式或匿名方法中使用 await 运算符
  • 异步方法既不能声明任何 in、ref 或 out 参数,也不能具有引用返回(ref 返回)值,但它可以调用具有此类参数的方法。
  • 使用async修饰的方法,async需位于返回类型前,如:public async void CountAsync()
  • 异步方法的返回类型必须是 void 或任务类型。 任务类型是 System.Threading.Tasks.Task 和构造自的类型 System.Threading.Tasks.Task<T>。
  • 对于除事件处理程序以外的代码,通常不鼓励使用 async void 方法,因为调用方不能 await 那些方法,并且必须实现不同的机制来报告成功完成或错误条件。
  • await 运算符的操作数通常是以下其中一个 .NET 类型:Task、Task<TResult>、ValueTask 或 ValueTask。 但是,任何可等待表达式都可以是 await 运算符的操作数。

二、命名规范

  • 按照约定,返回常规可等待类型的方法(例如 Task、Task<T>、ValueTask 和 ValueTask<T>)应具有以“Async”结束的名称。
  • 启动异步操作但不返回可等待类型的方法不得具有以“Async”结尾的名称,但其开头可以为“Begin”、“Start”或其他表明此方法不返回或引发操作结果的动词。

三、await/async的作用

await/async可以简化我们异步编程的代码,也让我们可以以一种类似同步编程的方式来进行异步编程,另外当我们需要不阻塞主线程异步执行,又要有顺序的执行相关代码的时候,await/async就可以排上用场。

四、基本使用

第一种情况:只有async ,没有await,有或无返回值

这种情况对于异步编程没什么意义,可以看作是一个同步方法,因为async和await一起使用才有意义。

第二种情况:async和await配套使用,返回值为void

在WinForm窗体中放置一个按钮,点击按钮,代码如下:

        private  void button2_Click(object sender, EventArgs e)
        {
            Debug.WriteLine("主线程--开始");
            TestVoidAsync();
            Debug.WriteLine("主线程--结束");
        }

        private async void TestVoidAsync()
        {
            Debug.WriteLine("开始执行TestVoidAsync方法");
            Task task = new Task(() =>
            {
                Debug.WriteLine("开始子线程耗时操作");
                Thread.Sleep(4000);
                Debug.WriteLine("结束子线程耗时操作");

            });
            task.Start();
            await task;
            Debug.WriteLine("await关键字后面的内容 1");
            Debug.WriteLine("await关键字后面的内容 2");

        }

在这里插入图片描述
我们会发现,使用await后,执行到await,主线程就是返回去做自己的事情,而await后面的内容将会在子线程执行完成后再继续完成。
在这里插入图片描述
当我们运行上面这两这个方法,可以方法运行结果基本一样,从这里看出await的作用和ContinueWith的作用类似,可以在上一任务以后接下一个任务,并且不会阻塞主线程。从上面案例来说,就是不会去卡界面。
另外需要注意:执行await后面的代码的线程可能是执行task的线程,也有可能是新开的线程,也有可能是主线程,这个是不确定的。

第三种情况:async和await配套使用,返回值Task

  • async Task 等效于 async void
  • 但是返回Task ,可以使用await,并且可以与Task.WhenAny, Task.WhenAll ContinueWith等组合使用,Void不可以
  • 建议使用async Task 这种形式(除事件处理程序外)

在这里插入图片描述
写法与async void 相比,没有什么太大的不一样,只是将void ,更换为Task而已,但是返回Task,可以使用await 以及一些Task的方法,如Task.WhenAny, Task.WhenAll ContinueWith等,更有利于后续逻辑的扩展。

如:我们可以在返回Task的方法执行完后使用ContinueWith 再接一个执行任务。

        public void TestTask()
        {            
            var task = TestTaskAsync();            
            task.ContinueWith((t) =>
            {
                Debug.WriteLine("这里是接着TestTaskAsync方法的执行代码。。。");
            });
        }

第四种情况:async和await配套使用,返回值Task<T>

  • 如果async和await配套使用的时候,需要返回值,则必须通过Task<T>的形式返回值
  • 如果不是,编译器会操作,如下图所示

在这里插入图片描述

        private async Task<int> TestTaskIntAsync()
        {
            Debug.WriteLine("开始执行TestTaskAsync方法");
            int result = 0;
            Task task = new Task(() =>
            {
                Debug.WriteLine("开始子线程耗时操作");
                Thread.Sleep(4000);
                Debug.WriteLine("结束子线程耗时操作");
                result = 10;
            });
            task.Start();
            await task;
            Debug.WriteLine("await关键字后面的内容 1");
            Debug.WriteLine("await关键字后面的内容 2");
            return result;
        }

该方法与我们平常返回一个int值的方法没什么太大区别,不过是多了async 和 await ,那么下面将演示如何取出异步方法的返回值。

        //方法一:使用ContinueWith
        private void button4_Click(object sender, EventArgs e)
        {
            Task<int> task = TestTaskIntAsync();
            task.ContinueWith((t) =>
            {
                Debug.WriteLine($"TestTaskIntAsync的返回值是:{t.Result.ToString()}");
            });
        }
        
        //方法二:使用await
        private async void button4_Click(object sender, EventArgs e)
        {
            Task<int> task = TestTaskIntAsync();
            await task;
            Debug.WriteLine($"TestTaskIntAsync的返回值是:{task.Result.ToString()}");
        }

第五种情况:不使用async和await,返回值Task<T>

  • 之所以列举出来是需要注意区别使用async和await 返回的Task<T> 和不使用async和await 返回Task<T>
        private Task<int> TestTaskInt()
        {
            Debug.WriteLine("开始执行TestTaskAsync方法");
            Task<int> task = new Task<int>(() =>
            {
                Debug.WriteLine("开始子线程耗时操作");
                Thread.Sleep(4000);
                Debug.WriteLine("结束子线程耗时操作");
                int result = 10;//模拟有返回值
                return result;
            });
            task.Start();
            return task;
        }

		//调用,获取返回值
        private void button4_Click(object sender, EventArgs e)
        {
            var task = TestTaskInt();
            task.ContinueWith((t)=> 
            {
                Debug.WriteLine(t.Result.ToString());
            });
        }

五、使用Async和Await实现多任务顺序执行且不阻塞

以微软文档的做早餐的案例加以简化来讲解

1.同步执行

using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

namespace ThreadTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();
            PourOJ();
            PourCoffee();
            ToastBread();
            FryBacon();
            FryEggs();
            Console.WriteLine("早餐已经做完!");
            stopwatch.Stop();
            Console.WriteLine($"做早餐总计耗时:{stopwatch.ElapsedMilliseconds}");
            Console.ReadLine();
        }

        //倒橙汁
        private static void PourOJ()
        {
            Thread.Sleep(1000);
            Console.WriteLine("倒一杯橙汁");
        }

        //烤面包
        private static void ToastBread()
        {
            Console.WriteLine("开始烤面包");
            Thread.Sleep(3000);
            Console.WriteLine("烤面包好了");

        }

        //煎培根
        private static void FryBacon()
        {
            Console.WriteLine("开始煎培根");
            Thread.Sleep(6000);
            Console.WriteLine("培根煎好了");
        }
        //煎鸡蛋
        private static void FryEggs()
        {
            Console.WriteLine("开始煎鸡蛋");
            Thread.Sleep(6000);
            Console.WriteLine("鸡蛋好了");
        }

        //倒咖啡
        private static void PourCoffee()
        {
            Thread.Sleep(1000);
            Console.WriteLine("倒咖啡");
        }
    }
}

在这里插入图片描述
以上案例同步执行,对于做早餐来说是非常的耗时的。

2.并行执行

如果此时我们每一项任务都有一个单独的人去完成
那么可以如下:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

namespace ThreadTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Test();
            Console.ReadLine();
        }

        private static void Test()
        {
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();
            List<Task> tasks = new List<Task>() { PourOJ(), ToastBread(), FryBacon(), FryEggs(), PourCoffee() };
            Task.WhenAll(tasks).ContinueWith((t)=> 
            {
                Console.WriteLine("早餐已经做完!");
                stopwatch.Stop();
                Console.WriteLine($"做早餐总计耗时:{stopwatch.ElapsedMilliseconds}");
            });
        }

        //倒橙汁
        private static async Task PourOJ()
        {
            await Task.Delay(1000);
            Console.WriteLine("倒一杯橙汁");
        }

        //烤面包
        private static async Task ToastBread()
        {
            Console.WriteLine("开始烤面包");
            await Task.Delay(3000);
            Console.WriteLine("烤面包好了");

        }

        //煎培根
        private static async Task FryBacon()
        {
            Console.WriteLine("开始煎培根");
            await Task.Delay(6000);
            Console.WriteLine("培根煎好了");
        }
        //煎鸡蛋
        private static async Task FryEggs()
        {
            Console.WriteLine("开始煎鸡蛋");
            await Task.Delay(6000);
            Console.WriteLine("鸡蛋好了");
        }

        //倒咖啡
        private static async Task PourCoffee()
        {
            await Task.Delay(1000);
            Console.WriteLine("倒咖啡");
        }
    }
}

在这里插入图片描述
可以看出来,由于所有的任务都并行执行的,因此做早餐的时间,明显的减少了。

3.并行且可指定顺序执行

现在呢,有个问题,不可能每次做早餐你都有那么多帮手,同时帮你,如果现在要求,先倒橙汁,然后倒咖啡,其余的操作并行执行,应该如何操作呢?

只需将以上案例的Test 方法修改如下:

        private static async void Test()
        {
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();
            await PourOJ();
            await PourCoffee();            
            List<Task> tasks = new List<Task>() { ToastBread(), FryBacon(), FryEggs() };
            await Task.WhenAll(tasks);
            Console.WriteLine("早餐已经做完!");
            stopwatch.Stop();
            Console.WriteLine($"做早餐总计耗时:{stopwatch.ElapsedMilliseconds}");
        }

在这里插入图片描述


总结

以上就是今天要介绍的内容,希望以上内容可以帮助到大家,如文中有不对之处,还请批评指正。
官方文档-异步编程

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

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

相关文章

spring依赖查找、依赖注入深入学习及源码分析

文章目录一、依赖查找1、单一类型依赖查找&#xff08;接口 - BeanFactory&#xff09;根据Bean名称查找根据Bean类型实时查找根据Bean类型延迟查找&#xff08;Spring 5.1&#xff09;根据Bean名称类型查找2、集合类型依赖查找&#xff08;接口 - ListableBeanFactory&#xf…

Metal每日分享,颜色转换滤镜效果

本案例的目的是理解如何用Metal实现像素颜色转换滤镜&#xff0c;通过对像素颜色的不同读取方式获取到相应像素颜色&#xff0c;灰度图移除场景中除了黑白灰以外所有的颜色&#xff0c;让整个图像灰度化&#xff1b; Demo HarbethDemo地址 实操代码 // 转成灰度图滤镜 let f…

js深拷贝浅拷贝与lodash

title: js深拷贝浅拷贝与lodash date: 2022/9/27 14:46:25 categories: lodashjs入门 深拷贝和浅拷贝 ref&#xff1a;JS传递参数时的内部存储逻辑 JS 變數傳遞探討&#xff1a;pass by value 、 pass by reference 還是 pass by sharing&#xff1f; 在这个问题前&#xff…

LeetCode HOT 100 —— 169.多数元素

题目 给定一个大小为 n 的数组 nums &#xff0c;返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。 你可以假设数组是非空的&#xff0c;并且给定的数组总是存在多数元素。 思路 方法一&#xff1a;哈希表 遍历整个数组&#xff0c;记录每个数值出…

JAVA IO详解

目录 一、流的概念与作用 二、Java IO的用途和特征 三、流的使用详解 一、流的概念与作用 流(Stream)&#xff1a; 在Java IO中&#xff0c;流是一个核心的概念。流从概念上来说是一个连续的数据传输过程。人们根据数据传输特性将流抽象为各种类&#xff0c;方便更直观的进…

【服务器数据恢复】服务器raid5崩溃导致上层应用不可用的数据恢复案例

服务器数据故障&#xff1a; 某公司服务器8块硬盘组成raid5磁盘阵列&#xff0c;其中有2块硬盘故障指示灯报警&#xff0c;其他硬盘指示灯正常&#xff0c;上层应用不可用。 服务器数据恢复过程&#xff1a; 1、服务器数据恢复工程师拿到故障服务器所有硬盘后对出现物理故障的…

ADI Blackfin DSP处理器-BF533的开发详解18:用触摸屏的例程来理解中断(含源码)

硬件准备 ADSP-EDU-BF533&#xff1a;BF533开发板 AD-HP530ICE&#xff1a;ADI DSP仿真器 软件准备 Visual DSP软件 硬件链接 硬件实现原理 ADSP-EDU-BF533 开发板上的中断资源连接到了 CPLD&#xff0c;并通过 CPLD 将中断信号连接到 PF0 触发&#xff0c;通过 CPLD映射的…

ubuntu交叉编译(armv7_32位)onnx源码_cpu版本

1 下载onnx git clone https://github.com/microsoft/onnxruntime cd onnxruntime git submodule update --init --recursive2 编译 由于是交叉编译&#xff0c;所以需要设置一下编译工具&#xff0c;在网上搜索看到了这个 chineseocr_lite/build-onnxruntime-android.sh a…

Jmeter(一):jmeter概述与工作原理,安装与基本配置介绍

Jmeter(1)&#xff1a;jmeter概述与工作原理 jmeter概述与工作原理 JMeter 是 Apache 基金会 Jakarta 上的一个纯 Java 开源项目&#xff0c;起初用于基 于 Web 的压力测试&#xff08;pressure test&#xff09;&#xff0c;后来其应用范围逐渐扩展到对文件传输 FTP, 大型数据…

基于JavaWeb+JSP的校园二手交易平台(源码+数据库+说明文档)

目录 一、前后端功能模块 1.用户web前端页面功能模块 2.后台信息管理模块 二、开发环境 三、开发技术 四、页面设计 1.登录注册界面 2.网页主页界面 3.商品列表界面 4.商品详情界面 5.支付页面 6.支付成功后页面 7.我的订单页面 ​8.个人已发布与待处理订单界面…

google外链重要性高吗?谷歌外链作用大不大

google外链重要性高吗&#xff1f; 答案是&#xff1a;非常重要&#xff0c;而且要注重建设付费GPB外链。 要相信有价值的外链一般都比较难获取&#xff0c;那种高流量的外链一般要靠自己去outreach, 但是成功率比较低&#xff0c;我们需要用金钱和优质外链资源去交换 做高质…

程序员的浪费,Python一对一还原《点燃我,温暖你》里面比较火的那个爱心代码 | 附源码

前言 包子们&#xff0c;上午好 最近有个剧挺火的 就是那个程序员的剧&#xff0c;叫《点燃我&#xff0c;温暖你》 最近听说很火呀&#xff0c;那作为程序员&#xff0c;Python中的战斗机的小编&#xff0c;能不给大家安排一波&#xff01; 怎么说呢&#xff0c;用这个表白也…

我凭借这 1000 道 java 真题,顺利拿下京东、饿了么、阿里大厂 offer

今天这篇文章也算是一次面试总结了吧&#xff01; 毕竟金九银十过去了&#xff0c;总得给大家来点东西交代交代&#xff01; 所以今天&#xff0c;这篇文章就应运而生了&#xff0c;给大家来点正正经经的干货教学&#xff0c;让大家体验一下干货的魅力&#xff01; 小编今天这里…

【C语言数据结构(基础篇)】第一站:时间复杂度与空间复杂度

目录 一、什么是时间复杂度和空间复杂度 1.算法效率 2.时间复杂度的概念 3.空间复杂度的概念 二、如何计算常见的时间复杂度 1.大O的渐进表示法 2.一些时间复杂度的例子 &#xff08;1&#xff09;例1 &#xff08;2&#xff09;例2 (3)例3 (4)例4 &#xff08;5&a…

【计算机视觉+自动驾驶】二、多任务深度学习网络并联式、级联式构建详细讲解(图像解释 超详细必看)

觉得有帮助麻烦点赞关注收藏~~~ 一、多任务网络的主要分类 目前建立的多任务网络可以分为两种方法&#xff0c;一种为并联多任务网络结构&#xff0c;另一种为级联多任务网络结构&#xff0c;两种网络构建方式分别如下图所示 并联式 级联式 并联网络结构大多为共享基础网络而…

ADI Blackfin DSP处理器-BF533的开发详解14:LED跑马灯(含源代码)

接口讲完了&#xff0c;下面写点应用程序&#xff0c;GPIO最典型的应用&#xff0c;LED跑马灯。 硬件准备 ADSP-EDU-BF533&#xff1a;BF533开发板 AD-HP530ICE&#xff1a;ADI DSP仿真器 软件准备 Visual DSP软件 硬件链接 *硬件实现原理 ADSP-EDU-BF533开发板上共设计了…

2005-2020年全国31省劳动力市场分割指数

2005-2020年全国31省劳动力市场分割指数 1、时间&#xff1a;2005-2020年 2、范围&#xff1a;包括全国31省&#xff0c; 3、数据内容&#xff1a;数据存在缺失&#xff0c;下载链接界面有数据预览&#xff0c;具体缺失情况参看链接内数据预览&#xff0c; 内含原始数据、A…

把废旧监控改无人机遥控车红外远程摄像头

像我们这些精打细算的业余玩家&#xff0c;淘个新宝贝都要掂量掂量。很羡慕能买到专用红外摄像头配无人机。可是手头不宽裕&#xff0c;只有一些旧零件。这都是废物再利用&#xff0c;所以说不要太追求性能了&#xff0c;自然让他工作就好&#xff0c;测试这条路线的可行性。 …

blneder 蜡笔

文章目录简介.打开蜡笔.基本操作.自由线.图形工具.图层.遮罩.画布.画布原点.![在这里插入图片描述](https://img-blog.csdnimg.cn/46cb7019e8ff41e6b391e056c616ce32.png)画布旋转.辅助.圆形.径向.平行.栅格.等距.编辑模式.顶部工具栏.选择.曲线编辑.左侧工具栏.快捷键.画笔深度…

值得一看的Linux内核—中断下半部之软中断

软中断 软中断&#xff08;softirq&#xff09;是中断处理程序在开启中断的情况下执行的部分&#xff0c;可以被硬中断抢占。 内核定义了一张软中断向量表&#xff0c;每种软中断有一个唯一的编号&#xff0c;对应一个softirq_action实例&#xff0c;softirq_action实例的成员…