最新要闻

广告

手机

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

家电

C#语言async, await 简单介绍与实例(入门级) 当前聚焦

来源:博客园

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

同步的基本意思是:程序一个个执行方法,或者说在方法调用上,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 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 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 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 = awaitGetJsonAsync(...);  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 task)    {        //  这里模拟对数字取其相反数        return task.Result * -1;    }}

下面显示了运行结果

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

关键词: