本文介绍异步编程的基本思想和语法。在程序处理里,程序基本上有两种处理方式:同步和异步。对于有些新手,甚至认为“同步”是同时进行的意思,这显然是错误的。

同步的基本意思是:程序一个个执行方法,或者说在方法调用上,fun1(), fun2(), fun3(),fun4().. 按顺序调用,而异步的意思是:方法不是按顺序执行,可能fun2执行的时间比较长

那就先执行fun3,fun4。等执行完了fun2 在执行后面的fun1,fun6,fun7..., 很显然,异步编程比同步编程复杂很多,因为他涉及到线程的同步。

注意:对于单核CPU来说,任一时刻只能执行一条指令,对于这种微观观点,我们不用太过于深究,因为操作系统会帮助我们调度。换句话说,我们一边打印word文档,一边听歌,一边写字

虽然我们感觉是“同时”进行的,但是其实是CPU是在后台不停的帮助我们切换进程,只是CPU切换的速度太快了,让我们感觉我们是在“同时”做很多件事。

(一)基本异步示例

下面代码演示了一个基本上异步程序:(程序使用VS2022+.NET 7.0 开发的)

(1)HandleFileAsync() 表示这是一个异步的方法,方法名称前有一个 await 关键字。

作为一个约定,方法总是以Async结尾,这样,使用者看到这个方法就知道了这是一个异步方法,这仅仅是方法名称的一个约定,不加Async不影响使用。

(2)在HandleFileAsync 方法里,模拟执行一些费时的操作。

(3)在HandleFileAsync 执行期间,不会阻塞主线程,现在输入字符串 123 ,系统会显示出入的结果。

(4)在异步方法执行完毕后,返回主线程,输出计数的结果。

using System;
using System.IO;
using System.Threading.Tasks; class Program
{
public static void Main()
{
// Part 1: 开始处理大文件文件
Task<int> task = HandleFileAsync(); // 在文件处理前,把控制权交给控制台
// 让用户输入一些文字
Console.WriteLine("请耐心等待,系统正在处理文件," +" 但是此时,你可以输入一些字母,回车后显示"); // 在文件处理时,同时读取你的输入
string line = Console.ReadLine();
Console.WriteLine("你刚刚输入的是: " + line); // Part 3: 等候处理结果
// 显示处理结果
task.Wait();
var x = task.Result;
Console.WriteLine("计数: " + x); Console.WriteLine("程序运行完毕!");
Console.ReadLine();
} static async Task<int> HandleFileAsync()
{
string file = @"C:\qmx\token.txt"; // Part 2: 下面开始处理大文件 Console.WriteLine("文件处理开始");
int count = 0; // 读取文件
using (StreamReader reader = new StreamReader(file))
{
string v = await reader.ReadToEndAsync(); // 处理数据
count += v.Length; // 这里是模拟代码,并没有实际的意义, 让程序执行1000万次,
// 纯粹是模拟这是一个耗时的操作
for (int i = 0; i < 1000000; i++)
{
int x = v.GetHashCode();
if (x == 0)
{
count--;
}
}
}
Console.WriteLine("文件处理结束");
return count;
}
}

下面显示的是运行结果

当然上面后面的代码可以简写为  var x =await task.Result;

 (二)线程阻塞(死锁)

在上面方法里,必须小心的调用 Wait方法,因为处理不好,很容易发生任务阻塞。 Stephen Cleary 曾经给了一个典型的例子:见

https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html

想象一下,我们有一个winForm应用程序,里面有一个Button,在Button的点击事件里,我们调用 HttpClient 的 GetStringAsync 方法获取返回的JSON字符串,然后把字符串显示在文本框里。

为此,我们编写了如下代码:

// Button的点击事件
public void Button1_Click(...)
{
//获取Web返回的字符串
var jsonTask = GetJsonAsync(...);
//把字符串显示在文本框里
textBox1.Text = jsonTask.Result;
} public static async Task<JObject> GetJsonAsync(Uri uri)
{
// 调用 HttpClient 的 GetStringAsync 方法获取JSON
using (var client = new HttpClient())
{
var jsonString = await client.GetStringAsync(uri);
return JObject.Parse(jsonString);
}
}

现在你运行上面的代码,当你点击按钮时,你会发现程序没有出现你所想要的结果:因为程序被卡死了,根本无法进行其他操作。
除了终止应用程序,你别无选择。为什么会发生什么死锁现象呢?

为了让通俗解释死锁看下面一个例子:假设我们有一把蓝钥匙,可以打开一扇蓝色门;以及一把红钥匙,可以打开一扇红色门。两把钥匙被保存在一个皮箱里。同时我们定义六种行为:获取蓝钥匙,打开蓝色门,归还蓝钥匙,获取红钥匙,打开红色门,归还红钥匙。如下图:你可以把6个行为理解为函数里6个方法 (以下内容改写自知乎

如果是同步编程,方法一个个调用,没有问题

但是,当异步调动时,每个方法顺序就不那么确定了,就可能出现如下这个情况

可以看到,当两个线程都运行到第三步的时候,线程A在等线程B归还红钥匙,线程B在等线程A归还蓝钥匙,因而两个线程都永远卡在那里无法前进。
这就是形成了死锁。

理解了上面的死锁,回头再来看为什么winForm里产生了死锁,主线程调用异步方法返回的结果,被告知方法未完成,因此主线程在等待方法完成。

当异步方法完成后,把自己状态告知主线程已经Compled时,但是主线程一直在繁忙状态,他在等待任务完成,因此,发生了死锁。

这告诉我们在异步编程时,要特别需要注意死锁的问题。作为一个简单的解决方法:只要加一个await 异步就可以了

public async void Button1_Click(...)
{
var json = await GetJsonAsync(...);
textBox1.Text = json;
}

这也就是大家常说“一路异到底”。(不要在同步方法里调用异步方法,要异步调用异步,一路异到底)

(三)ContinueWith

在现实世界里,经常会发生在一个方法完成之后,在进行下一个方法的调用,
例如,在Button 事件里 (1)异步从网络获取HTML源代码。 (2)把源代码写入 C:\File.txt 里

这就需要第二步骤需要在第一步完成之后运行,此时需要用到ContinueWith 方法。

下面的代码简单演示了 ContinueWith  (其实,ContinueWith 这个方法的名字就已经很好的解释了他的作用)

using System;
using System.Threading.Tasks; class Program
{
static void Main()
{
//调用10次异步方法
for (int i = 0; i < 10; i++)
{
Run2Methods(i);
}
//所有调用都是异步
Console.ReadLine();
} static async void Run2Methods(int count)
{
// 在调动完后,调用 ContinueWith 继续操作
int result = await Task.Run(() => GetSum(count))
.ContinueWith(task => MultiplyNegative1(task));
Console.WriteLine("Run2Methods 结果: " + result);
} static int GetSum(int count)
{
//这里模拟一些额外操作
int sum = 0;
for (int z = 0; z < count; z++)
{
sum += (int)Math.Pow(z, 2);
}
return sum;
} static int MultiplyNegative1(Task<int> task)
{
// 这里模拟对数字取其相反数
return task.Result * -1;
}
}

下面显示了运行结果

上面简单的介绍了异步编程。

C#语言async, await 简单介绍与实例(入门级)的更多相关文章

  1. Linux守护进程简单介绍和实例具体解释

    Linux守护进程简单介绍和实例具体解释 简单介绍 守护进程(Daemon)是执行在后台的一种特殊进程.它独立于控制终端而且周期性地执行某种任务或等待处理某些发生的事件.守护进程是一种非常实用的进程. ...

  2. Tstrings类简单介绍及实例

    用TStrings保存文件;var  S: TStrings;begin  S := TStringList.Create();  { ... }  S.SaveToFile('config.txt' ...

  3. 异步async/await简单应用与探究

    感谢Marco CAO指出的两点错误,已做出修改与补充 异步函数(async/await)简单应用 .NET Framework4.5提供了针对异步函数语法糖,简化了编写异步函数的复杂度. 下面通过一 ...

  4. 《R语言入门》语言及环境简单介绍

    简单介绍 watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/diss ...

  5. Dubbo简单介绍及实例

    1.概念 Dubbo是一个分布式服务框架,以及阿里巴巴内部的SOA服务化治理方案的核心框架.其功能主要包含:高性能NIO通讯及多协议集成.服务动态寻址与路由.软负载均衡与容错,依赖分析与降级等. 说通 ...

  6. Solr之NamedList 简单介绍与实例解析

    大家都知道,Solr是一个基于Lucene高可配置的搜索服务器,大部分参数值以及相关优化等等都可以在solrconfig.xml中配置,那么就需要一个能够很快的进行解析和读取配置文件内容的数据结构,为 ...

  7. async/await简单使用

    function process(i) { var p = new Promise(function(resolve,reject){ setTimeout(function(){ console.l ...

  8. Swift async await 使用介绍

    // // ViewController.swift // AsynWait // // Created by shengjie on 2022/2/9. // import UIKit class ...

  9. Entity Framework 的简单介绍与实例

    1.下载与引用 a) 首先需要下载一个oracle clinent 12c 发行版(我这边下载的是发行版)并进行安装,下载内容如下图 B) 创建一个项目,通过Nuget引用  添加ODP.NET    ...

  10. (八十七)AutoLayout的简单介绍与实例

    AutoLayout是继AutoResizing之后的一种自己主动布局方法.攻克了AutoResizing无法处理控件间相互关系的问题. AutoLayout在storyboard中通过底部工具条设置 ...

随机推荐

  1. ACM-NEFU15届校赛-大二组

    A.小林找工作 #include<bits/stdc++.h> using namespace std; const int MAXN=1e5+10; int p[MAXN]; int m ...

  2. 【Jenkins系列】-Pipeline语法全集

    Jenkins为您提供了两种开发管道代码的方式:脚本式和声明式. 脚本式流水线(也称为"传统"流水线)基于Groovy作为其特定于域的语言. 而声明式流水线提供了简化且更友好的语法 ...

  3. mysql迁移:docker迁入迁出mysql

    docker迁出mysql数据库 测试环境: docker服务器 mysql服务器 IP 192.168.163.19 192.168.163.16 操作系统 CentOS7.8 CentOS7.8 ...

  4. docker启动mysql注意事项

    1.编码问题 登录mysql伪终端 mysql查看编码 show variables like 'character%'; 宿主机在conf.d中添加配置my.cnf文件 [client] defau ...

  5. vue3的setup语法糖

    https://blog.csdn.net/weixin_44922480/article/details/127337914 https://blog.csdn.net/m0_63108819/ar ...

  6. 如何将 Spire.Doc for C++ 集成到 C++ 程序中

    Spire.Doc for C++是一个专业的 Word 库,供开发人员在任何类型的 C++ 应用程序中阅读.创建.编辑.比较和转换 Word 文档. 本文演示了如何以两种不同的方式将 Spire.D ...

  7. 深度学习03-(图像梯度处理、图像轮廓、图像预处理在AI中的应用)

    深度学习03-计算机视觉基本理论2 深度学习03-(计算机视觉基本理论2) 图像梯度处理 什么是图像梯度 模板运算 均值滤波 高斯滤波 中值滤波 边沿检测 锐化 图像轮廓 什么是图像轮廓 查找和绘制轮 ...

  8. Rust中的函数指针

    什么是函数指针 通过函数指针允许我们使用函数作为另一个函数的参数.函数的类型是 fn (使用小写的 "f" )以免与 Fn 闭包 trait 相混淆.fn 被称为 函数指针(fun ...

  9. Linux 给用户赋予操作权限

    赋予local目录读写权限给keesail,别的用户对这个目录没有任何权限. chown -R keesail:keesail ./local chmod 777 文件夹名称,可以把文件夹设置成所有用 ...

  10. 2020-11-22:mysql中,什么是filesort?

    福哥答案2020-11-22:[答案来自此链接:](http://bbs.xiangxueketang.cn/question/412)如果mysql在排序的时候没有使用到索引那么就会输出 using ...