C#-Async关键字(异步方法)

2023-11-01

async关键字(异步方法)

async关键字是C#特有的。Java没有这玩意。
async在C#世界里是上下文关键字。它只有在修饰一个方法的时候才自动被编译器识别为关键字,在代码的其他位置上可以被用作变量名等其他任何用途。
async关键字用来修饰两类方法: lambda表达式或者异步方法。
拥有async修饰的方法称为async方法,比如:

public async Task<int> ExampleMethodAsync()  
{  
     // (1) To do some code here synchronously...
    await .....//  (2) To do something asynchronously....
    // (3) To do some code here after awiat code...
} 

就如上面这个方法ExampleMethodAsync(),微软爷爷特别喜欢在定义异步函数名字后习惯加个Async后缀(这不是必须的,加不加编译器既不会报错,也不会影响异步特性),告诉我们这个方法是个异步方法。我们在自己定义异步方法的时候,也可以照搬这个微软的习惯。
async修饰的方法内部,应当出现一个await关键字,两个关键字一般成对出现。当然,如果我们不小心忘记写await表达式或者语句,这个async方法默认按照同步方式运行,同时,编译器会友好地提示我们是不是漏写了await。此外,async方法内部,可以有多个await语句。
awiat运行的语句,一般都是比较费时的任务(也就是会阻塞主线程的一些操作,比如获取Http应答,写入文档,保存数据库等),要不然就不需要异步了。
以上面的例子为例(假设例子中的await是第一个await),异步方法执行过程(比较粗的看):

  1. 主线程进入方法ExampleMethodAsync()后,先顺序执行(1); 如果(1)当中有创建Task或Task<TResult>的语句,或者是调用其他async方法(返回值是Task后者Task<TResult>),为了描述方便,我们都称为Task创建语句; 比如直接创建一个Task或Task<TResult>:
var tsk = Task.Run(()=>{
	Thread.Sleep(1000);
	Console.Writeline("Do another job asynchronously.");
});

或者调用另外一个async方法:

Task<string> getStringTask = client.GetStringAsync("https://docs.microsoft.com/dotnet");

那么,在调用完Task创建语句的时候异步任务就已经开始运行了(这个语句调用本身是在主线程当中,内部的任务则是新的线程中执行),也就是此时异步的线程已经启动了,由于它是异步启动的,所以它并不会阻止主线程继续往下走;

  1. 接下来,主线程会顺序运行到async方法内部的第一个await,如果第一个await调用的仍然是一个async方法,那么主程序继续进入这个方法执行,一直到碰到一个await task为止,主线程才会跳出ExampleMethodAsync方法; 举个例子:
	static void  Main(string[] args)
	{
		// do something...
		ExampleMethodAsync();
		// do someting else...
	}
    public static async void ExampleMethodAsync()
    {
        // (1) 执行一些任务Do2Async()前准备的事情...
        await Do2Async(); // (2)
        // (3) 运行一些Do2Async()执行完之后的事情...
    }
	public static Task Do2Async()
	{
	    // 执行一些t任务执行前的事情,比如任务的准备...
	    Task t = Task.Run(()=>{
	    // 异步任务中执行费时的事情...
	    });
	    // 运行一些与t无关的事情...
	    await t;
	    // 在这里执行一些t任务执行完相关的事情...
	}

调用方(也就是main所在的主线程)会一直执行到20行才跳出ExampleMethodAsync()方法,而不是在第10行。

  1. ExampleMethodAsync()方法中剩余的(3)在执行完await(2)部分的内容才执行。
  2. 假设ExampleMethodAsync()中有第二个,第三个…awiat,因为主程序已经跳出来了,后续的await会在异步线程中按顺序执行下去。

async方法可以是下面三种返回类型:

  • Task
  • Task<TResult>
  • void 这种返回类型一般用在event事件处理器中,或者用在你只需要任务执行,不关心任务执行结果的情况当中。
  • 任何其他具有GetAwaiter方法的类型(从C#7.0开始)

注意,我们无法等待(awiat)一个async void 方法。

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

namespace test
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine($"ThreadID:{Thread.CurrentThread.ManagedThreadId}  Hello, I am Caller!");
            DoAsync();
            Console.WriteLine($"ThreadID:{Thread.CurrentThread.ManagedThreadId}  Hello, I am Caller too!");
            Console.Read();
        }
        public static async void DoAsync()
        {
            System.Console.WriteLine($"ThreadID:{Thread.CurrentThread.ManagedThreadId}  In DoAsync(), before SunAsync()");
            await SunAsync();
            System.Console.WriteLine($"ThreadID:{Thread.CurrentThread.ManagedThreadId}  After SunAsync(), DoAsync() End.");
        }
        public static async Task SunAsync()
        {
            var t = Task.Run(()=>{
                    System.Console.WriteLine($"ThreadID:{Thread.CurrentThread.ManagedThreadId}  New Task~");
                    for(int i=0 ; i<10; i++)
                    {
                        Thread.Sleep(1000);
                        System.Console.WriteLine($"ThreadID:{Thread.CurrentThread.ManagedThreadId}  I am playing game...");                    
                    }
                });
            System.Console.WriteLine($"ThreadID:{Thread.CurrentThread.ManagedThreadId}  After Task, before await.");
            await t;
            System.Console.WriteLine($"ThreadID:{Thread.CurrentThread.ManagedThreadId}  After await, before SunAsync() exit.");
        }
    }
}

此时的结果:

ThreadID:1  Hello, I am Caller!
ThreadID:1  In DoAsync(), before SunAsync()
ThreadID:1  After Task, before await.
ThreadID:4  New Task~
ThreadID:4  I am playing game...
ThreadID:4  I am playing game...
ThreadID:4  I am playing game...
ThreadID:4  I am playing game...
ThreadID:4  I am playing game...
ThreadID:4  I am playing game...
ThreadID:4  I am playing game...
ThreadID:4  I am playing game...
ThreadID:4  I am playing game...
ThreadID:4  I am playing game...
ThreadID:1  After await, before SunAsync() exit.
ThreadID:1  After SunAsync(), DoAsync() End.
ThreadID:1  Hello, I am Caller too!
仔细阅读这段代码和结果,细心体会,这段代码是ansync void方法嵌入调用ansync Task方法。要注意体会,并不是说一遇到await主程序(ansync 方法的调用方)就立即退出DoAsync()方法,而是执行到33行,碰到了第一个的Task才跳出来。 从这个例子的输出ThreadID号中,可知,33行await之后的内容都是在新的线程(4线程)中运行的。而33行await之前的内容都在主线程(1线程)中运行。

如果将SunAsync()代码改为(await之前增加一个Thread.Sleep(150000)):


  public static async Task SunAsync()
        {
            var t = Task.Run(()=>{
                    System.Console.WriteLine($"ThreadID:{Thread.CurrentThread.ManagedThreadId}  New Task~");
                    for(int i=0 ; i<10; i++)
                    {
                        Thread.Sleep(1000);
                        System.Console.WriteLine($"ThreadID:{Thread.CurrentThread.ManagedThreadId}  I am playing game...");                    
                    }
                });
            System.Console.WriteLine($"ThreadID:{Thread.CurrentThread.ManagedThreadId}  After Task, before await.");
            Thread.Sleep(15000); //主线程睡15秒
            await t;
            System.Console.WriteLine($"ThreadID:{Thread.CurrentThread.ManagedThreadId}  After await, before SunAsync() exit.");
        }

ThreadID:1 Hello, I am Caller!
ThreadID:1 In DoAsync(), before SunAsync()
ThreadID:1 After Task, before await.
ThreadID:4 New Task~
ThreadID:4 I am playing game…
ThreadID:4 I am playing game…
ThreadID:4 I am playing game…
ThreadID:4 I am playing game…
ThreadID:4 I am playing game…
ThreadID:4 I am playing game…
ThreadID:4 I am playing game…
ThreadID:4 I am playing game…
ThreadID:4 I am playing game…
ThreadID:4 I am playing game…
ThreadID:1 After await, before SunAsync() exit.
ThreadID:1 After SunAsync(), DoAsync() End.
ThreadID:1 Hello, I am Caller too!

因为,Task.Run()的任务在运行到await之前就结束了,因此,await后的内容仍然在主线程(1线程)中执行。这个例子告诉我们,如果任务在await之前就已经执行完毕,那么await后的内容仍然保留在原线程中执行。

总之,async方法调用方在碰到一个实际的await task的时候才退出async方法体。一般在await之前处理与异步任务无关的事情(这部分代码是由异步方法的调用方所在的线程执行),await之后的代码则是处理异步任务处理完后的事情,因此这部分代码就可以处理与异步任务相关的事情(这部分一般来说是在新建的异步线程中执行的,除非在调用await之前任务就已经很快的执行完了,那么这部分内容也可能仍然在调用方线程中执行)。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C#-Async关键字(异步方法) 的相关文章

  • 检查两个数是否是彼此的排列?

    给定两个数字 a b 使得 1 例如 123 是 312 的有效排列 我也不想对数字中的数字进行排序 如果您指的是数字的字符 例如 1927 和 9721 则 至少 有几种方法 如果允许排序 一种方法是简单地sprintf将它们放入两个缓冲
  • 无法使用已与其底层 RCW 分离的 COM 对象。在 oledb 中

    我收到此错误 但我不知道我做错了什么 下面的代码在backrgroundworker中 将异常详细信息复制到剪贴板 System Runtime InteropServices InvalidComObjectException 未处理 通
  • 获取按下的按钮的返回值

    我有一个在特定事件中弹出的表单 它从数组中提取按钮并将标签值设置为特定值 因此 如果您要按下或单击此按钮 该函数应返回标签值 我怎样才能做到这一点 我如何知道点击了哪个按钮 此时代码返回 DialogResult 但我想从函数返回 Tag
  • pthread_cond_timedwait() 和 pthread_cond_broadcast() 解释

    因此 我在堆栈溢出和其他资源上进行了大量搜索 但我无法理解有关上述函数的一些内容 具体来说 1 当pthread cond timedwait 因为定时器值用完而返回时 它如何自动重新获取互斥锁 互斥锁可能被锁定在其他地方 例如 在生产者
  • linux perf:如何解释和查找热点

    我尝试了linux perf https perf wiki kernel org index php Main Page今天很实用 但在解释其结果时遇到了困难 我习惯了 valgrind 的 callgrind 这当然是与基于采样的 pe
  • 如何在列表框项目之间画一条线

    我希望能够用水平线分隔列表框中的每个项目 这只是我用于绘制项目的一些代码 private void symptomsList DrawItem object sender System Windows Forms DrawItemEvent
  • 使闭包捕获的变量变得易失性

    闭包捕获的变量如何与不同线程交互 在下面的示例代码中 我想将totalEvents 声明为易失性的 但C 不允许这样做 是的 我知道这是错误的代码 这只是一个例子 private void WaitFor10Events volatile
  • 实时服务器上的 woff 字体 MIME 类型错误

    我有一个 asp net MVC 4 网站 我在其中使用 woff 字体 在 VS IIS 上运行时一切正常 然而 当我将 pate 上传到 1and1 托管 实时服务器 时 我得到以下信息 网络错误 404 未找到 http www co
  • 如何返回 json 结果并将 unicode 字符转义为 \u1234

    我正在实现一个返回 json 结果的方法 例如 public JsonResult MethodName Guid key var result ApiHelper GetData key Data is stored in db as v
  • 在 ASP.NET Core 3.1 中使用包含“System.Web.HttpContext”的旧项目

    我们有一些用 Net Framework编写的遗留项目 应该由由ASP NET Core3 1编写的API项目使用 问题是这些遗留项目正在使用 System Web HttpContext 您知道它不再存在于 net core 中 现在我们
  • vector 超出范围后不清除内存

    我遇到了以下问题 我不确定我是否错了或者它是一个非常奇怪的错误 我填充了一个巨大的字符串数组 并希望在某个点将其清除 这是一个最小的例子 include
  • 如何将单个 char 转换为 int [重复]

    这个问题在这里已经有答案了 我有一串数字 例如 123456789 我需要提取它们中的每一个以在计算中使用它们 我当然可以通过索引访问每个字符 但是如何将其转换为 int 我研究过 atoi 但它需要一个字符串作为参数 因此 我必须将每个字
  • clang 实例化后静态成员初始化

    这样的代码可以用 GCC 编译 但 clang 3 5 失败 include
  • 从库中捕获主线程 SynchronizationContext 或 Dispatcher

    我有一个 C 库 希望能够将工作发送 发布到 主 ui 线程 如果存在 该库可供以下人员使用 一个winforms应用程序 本机应用程序 带 UI 控制台应用程序 没有 UI 在库中 我想在初始化期间捕获一些东西 Synchronizati
  • 当操作繁忙时,表单不执行任何操作(冻结)

    我有一个使用 C 的 WinForms 应用程序 我尝试从文件中读取一些数据并将其插入数据表中 当此操作很忙时 我的表单冻结并且无法移动它 有谁知道我该如何解决这个问题 这可能是因为您在 UI 线程上执行了操作 将文件和数据库操作移至另一个
  • Discord.net 无法在 Linux 上运行

    我正在尝试让在 Linux VPS 上运行的 Discord net 中编码的不和谐机器人 我通过单声道运行 但我不断收到此错误 Unhandled Exception System Exception Connection lost at
  • const、span 和迭代器的问题

    我尝试编写一个按索引迭代容器的迭代器 AIt and a const It两者都允许更改容器的内容 AConst it and a const Const it两者都禁止更改容器的内容 之后 我尝试写一个span
  • mysql-connector-c++ - “get_driver_instance”不是“sql::mysql”的成员

    我是 C 的初学者 我认为学习的唯一方法就是接触一些代码 我正在尝试构建一个连接到 mysql 数据库的程序 我在 Linux 上使用 g 没有想法 我运行 make 这是我的错误 hello cpp 38 error get driver
  • 如何使用 std::string 将所有出现的一个字符替换为两个字符?

    有没有一种简单的方法来替换所有出现的 in a std string with 转义 a 中的所有斜杠std string 完成此操作的最简单方法可能是boost字符串算法库 http www boost org doc libs 1 46
  • 恢复上传文件控制

    我确实阅读了以下帖子 C 暂停 恢复上传 https stackoverflow com questions 1048330 pause resume upload in c 使用 HTTP 恢复上传 https stackoverflow

随机推荐

  • PHP 技巧:使用分隔符将字符串拆分为数组

    在 PHP 中 将字符串拆分为数组是一项常见任务 可以使用各种函数和技术来完成 这 explode 函数是一个方便高效的选项 它允许您根据分隔符将字符串拆分为数组 在本文中 我们将探讨如何在 PHP 中使用explode 函数将字符串拆分为
  • Visual Studio Code 中 Git 集成的快捷方式

    Git 是一种流行的版本控制系统 开发人员使用它来跟踪代码更改并与其他人在项目上进行协作 Visual Studio Code VS Code 具有内置的 Git 集成 可以轻松地直接从编辑器管理和提交更改 在本文中 我们将讨论 Visua
  • 如何通过 SSH 下载和上传文件

    SSH 是访问远程服务器最安全的协议 它通过通信通道提供最高级别的端到端数据安全性 这SCP 安全复制 命令使用 SSH 协议在远程和本地服务器之间复制文件 远程服务器必须有正在运行的 SSH 服务器 本教程将帮助您了解通过 SSH 协议下
  • 如何在 CentOS/RHEL 7 上创建没有 Shell 访问权限的 SFTP 用户

    本教程将帮助您在 CentOS 和 RedHat 系统上创建仅 SFTP 用户 无 ssh 访问权限 用户只能通过SFTP访问连接服务器 并允许访问指定目录 用户无法通过 SSH 访问服务器 按照以下教程创建仅限 sftp 的帐户 第 1
  • 如何在 Linux 中编译和运行 C/C++ 程序

    C 是一种强大的结构化编程语言 用于开发系统软件 根据设计 C 提供了可以有效映射到典型机器指令的结构 它是由丹尼斯 里奇在贝尔实验室 C 程序源是自由格式的文本 使用分号作为语句终止符 使用大括号对条件 函数或循环等语句块进行分组 C 是
  • 如何在 CentOS 7 上安装 Drupal

    Drupal 是全球领先的开源 CMS 平台之一 它灵活 可扩展 可用于构建不同类型的网站 从小型个人博客到大型企业 政治和政府网站 在本教程中 我们将解释如何在 CentOS 7 上安装 Drupal 8 6 安装 Drupal 的方法有
  • 如何删除 Docker 容器、映像、卷和网络

    Docker 是一个开源容器化平台 允许您快速构建 测试和部署应用程序作为几乎可以在任何地方运行的便携式容器 使用 Docker 时 您可以快速积累大量未使用的对象 这些对象会消耗大量磁盘空间并使 Docker 命令生成的输出变得混乱 Do
  • 如何删除 Git 中未跟踪的文件

    Git 工作目录中的文件可以是跟踪的 也可以是非跟踪的 跟踪的文件是 Git 知道的已添加和提交的文件 跟踪的文件可以是未修改的 已修改的或暂存的 工作目录中的所有其他文件都未被跟踪 并且 git 不知道这些文件 有时 您的 git 工作目
  • 如何在 Bash 中逐行读取文件

    在编写 Bash 脚本时 有时您会发现自己需要逐行读取文件 例如 您可能有一个文本文件 其中包含应由脚本处理的数据 在本教程中 我们将讨论如何在 Bash 中逐行读取文件 逐行读取文件语法 逐行读取文件的最通用语法如下 while IFS
  • 如何在 CentOS 8 上安装 MySQL

    MySQL是最流行的开源关系数据库管理系统 最新版本的 MySQL 数据库服务器 8 0 版可从默认的 CentOS 8 存储库安装 MySQL 8 0引入了许多新功能和更改 这使得一些应用程序与该版本不兼容 在选择要安装的 MySQL 版
  • 如何在 Python 中删除(移除)文件和目录

    Python 有一些内置模块 允许您删除文件和目录 本教程介绍如何使用以下函数删除文件和目录os pathlib and shutil模块 删除文件 在Python中你可以使用os remove os unlink pathlib Path
  • 如何在CentOS上安装RPM包

    RPM 是 Red Hat 及其衍生产品 例如 CentOS 和 Fedora 使用的打包系统 CentOS 官方存储库包含数千个 RPM 软件包 可以使用yum命令行实用程序 通过启用适当的存储库 可以轻松安装标准 CentOS 存储库中
  • 如何在 Ubuntu 18.04 上安装 MongoDB

    MongoDB 是一个免费的开源文档数据库 它属于 NoSQL 数据库家族 与 MySQL 和 PostgreSQL 等传统的基于表的 SQL 数据库不同 在 MongoDB 中 数据存储在灵活的 类似 JSON字段可能因文档而异的文档 它
  • 如何允许远程连接 MySQL 数据库服务器

    默认情况下 MySQL 服务器仅侦听来自本地主机的连接 这意味着它只能由同一主机上运行的应用程序访问 然而 在某些情况下 需要从远程位置访问MySQL服务器 例如 您可能需要从本地系统或多服务器部署连接到远程 MySQL 服务器 其中应用程
  • 如何在 Debian 10 上安装和使用 FFmpeg

    FFmpeg 是一个用于处理多媒体文件的免费开源工具集合 它包含一组共享的音频和视频库 例如libavcodec libavformat和libavutil 使用 FFmpeg 您可以在各种视频和音频格式之间进行转换 设置采样率 捕获流音频
  • 如何更改 Git 远程 URL

    Git Remote 是一个指针 指向通常托管在远程服务器上的存储库的另一个副本 在某些情况下 例如当远程存储库迁移到另一台主机时 您需要更改远程的 URL 本指南介绍如何更改 Git 远程的 URL 更改 Git 远程的 URL 每个 G
  • 如何在 Debian 10 Linux 上安装 OpenCV

    OpenCV 开源计算机视觉库 是一个开源计算机视觉库 具有 C Python 和 Java 的绑定 它具有广泛的应用 包括医学图像分析 拼接街景图像 监控视频 检测和识别人脸 跟踪移动物体 提取 3D 模型等等 OpenCV可以利用多核处
  • linux下反弹shell

    最近做了几题都是反弹shell 总结一哈 bash bash i gt dev tcp vps ip 端口号 0 gt 1 bash i 生成一个交互式的子进程 表示在Linux后台运行 dev tcp vps ip 端口号 其实是与主机建
  • R语言练习题答案(10)第七章可视化数据挖掘工具Rattle

    关注公众号凡花花的小窝 含有更多更全面的计算机专业编程考研相关知识的文章还有资料 选择题 1 C 2 D 3 A 4 D 5 C 6 A 7 B 8 D 9 B 10 C 代码 7 1 install packages RGtk2 inst
  • C#-Async关键字(异步方法)

    async关键字 异步方法 async关键字是C 特有的 Java没有这玩意 async在C 世界里是上下文关键字 它只有在修饰一个方法的时候才自动被编译器识别为关键字 在代码的其他位置上可以被用作变量名等其他任何用途 async关键字用来