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

同步的基本意思是:程序一个个执行方法,或者说在方法调用上,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. 记录关于Chromium系浏览器密码安全问题的一些思考

    首先就是在此之前就看到有相关报道讲到Chrome等浏览器密码都在本地明文存储,而且权限要求很低,任何程序都可以随意读取,这方面的安全问题暂且不表. 今天使用Edge时候发现浏览器储存的密码,在我已经设 ...

  2. 一些随笔 No.1

    耦合 耦合是一个设计与逻辑上的问题 例如一个软件有20个功能,删除任意一个功能对别的19个功能不造成影响,就是低耦合 如果删除一个功能后其他功能会失去完整性,那么就是高耦合 Difference be ...

  3. oracle删除一张表后,索引,同义词,视图,约束会被删除么

    问题描述:看到有一道题,说删除一张表之后,什么会被关联删除 进行测试,看看一张表什么会被关联删除,进行scoot下的EMP进行测试 一.创建测试需求用例 表结构: SQL> desc emp; ...

  4. Redis缓冲区溢出及解决方案

    缓冲区(buffer),是内存空间的一部分.也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区. 一.Redis缓冲区溢出影响 在Redis ...

  5. ChatGPT API接口编程基础与使用技巧

    总结/朱季谦 趁着这周末空闲时间,在研读完OpenAi官网文档的基础上,及时总结了这篇<ChatGPT API接口编程基础与使用技巧>. 本文大部分内容是围绕编程方面,包括ChatGPT模 ...

  6. 【译】ConfigureAwait FAQ

    .NET 在数年前就在语言和库中添加了 async/await.在那段时间里,它像野火一样蔓延开来,不仅在 .NET 生态系统中,而且在无数其他语言和框架中被复制.在 .NET 中也看到了大量的改进, ...

  7. VS 查看引用的DLL/Nuget包源码时,无法看到注释

    一.问题描述 在下面的截图中,我们发现,源码有添加一段注释. 然后通过Nuget包引用,在VS中用Reshaper反编译时,发现没有注释: 原来,DLL是默认不带注释的.即你生成一个DLL,给另一个项 ...

  8. 【机器学习与深度学习理论要点】20. 什么是激活函数,为什么要用激活函数,常见的激活函数和特点,softmax函数

    1)什么是激活函数,为什么要用激活函数? 激活函数,指神经网络中将输入信号的总和转换为输出信号的函数,激活函数将多层感知机输出转换为非线性,使得神经网络可以任意逼近任何非线性函数,这样神经网络就可以应 ...

  9. cf1774f解题报告

    Magician and Pigs 分析一下三个操作分别干了些什么 新添一只猪 使血量为 \(x\) 的猪血量变为 \(\max(x-v,0)\) 设前面操作后猪总共会受到 \(s\) 的伤害,复制一 ...

  10. 安装anaconda遇到的一些问题

    文章目录 前言 一.报错:jupyter notebook:Bad file descriptor (C:\ci\zeromq_1602704446950\work\src\epoll.cpp:100 ...