• 2.2异步编程


    2.2异步编程

    用async关键字修饰方法后,就变成了异步方法。需要注意几点:

    • 异步方法返回值一般是Task。
    • 约定异步方法名字以Async为结尾。
    • 如果异步方法没有返回值,最好把返回值声明为非泛型的Task类型。
    • 调用泛型异步方法时,一般在前面加上await,这样方法调用的返回值就是T类型。
    • 如果一个方法中有await调用,则方法必须声明为async。

    await关键字的意思是:调用异步方法,等异步方法执行结束后再继续向下执行。如果不加await,则不等待异步方法执行结束,就向下执行。

    async Task<int> DownloadAsync(string url, string destFilePath)
    {
    	using HttpClient httpClient = new HttpClient();
    	string body = await httpClient.GetStringAsync(url);
    	await File.WriteAllTextAsync(destFilePath, body);
    	return body.Length;
    }
    
    Console.WriteLine("开始下载人邮社网站");
    int i1 = await DownloadAsync("https://www.ptpress.com.cn", "d:/ptpress.html");
    Console.WriteLine($"下载完成,长度{i1}");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    async异步方法的本质是:在对异步方法进行await调用时的等待时间(比如等待下载),会把当前的线程返回到线程池,等异步方法调用结束后,再从线程池中取出一个线程执行后面的代码。

    使用async异步方法要用多任务的概念,如果是一个任务,则使用常规的方法更好。例如餐厅客人叫服务员点菜,当来了多个客人时,某个客人叫服务员来点菜,服务员将菜单给到这个客人,然后不需要一直等着,等到这个客人点菜成功,再叫服务员,这个时候服务员拿着点完的菜单交给厨师。这样做的好处是,这个服务员可以在某个客人点菜的时候,去服务其他顾客。

    异步方法使用的技术是线程池,不等通于多线程。

    • 有async无await

    对于async方法,编译器会把代码根据await调用分为若干片段,对于不同的片段使用状态机的方式切换执行。

    //这个虽然是async方法,内部并没有await,它的内部并没有将任务放到其他线程中去执行
    //这样写和普通方法一样
    static async Task<decimal> CalcAsync(int n)
    {
        Console.WriteLine("CalcAsync:" + Thread.CurrentThread.ManagedThreadId);
        decimal result = 1;
        Random rand = new Random();
        for (int i = 0; i < n * n; i++)
        {
            result = result + (decimal)rand.NextDouble();
        }
        return result;
    }
    //使用Task.Run方法,把要执行的代码以委托的形式传递给Task.Run,这样系统会从线程池中取出一个线程去执行代码
    //这样才实现了异步
    async Task<decimal> CalcAsync(int n)
    {
    	Console.WriteLine("CalcAsync:" + Thread.CurrentThread.ManagedThreadId);
    	return await Task.Run(() => {
    		Console.WriteLine("Task.Run:" + Thread.CurrentThread.ManagedThreadId);
    		decimal result = 1;
    		Random rand = new Random();
    		for (int i = 0; i < n * n; i++)
    		{
    			result = result + (decimal)rand.NextDouble();
    		}
    		return result;
    	});
    }
    //由此可见:
    //异步方法的定义中,也需要使用线程池的操作
    //在调用异步方法使用await时,await有2个作用:
    //1、等待异步方法执行结束再往下执行,因为异步方法的定义中使用了线程池操作(Task.Run),按照正常的执行程序,他会在新线程中执行,而不会等待其执行完成,所以在线程池操作前面也加了await以示和普通线程池操作的区别。
    //2、会自动将Task类型的返回值识别为T类型
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    只要返回值是Task类型,就可以使用await关键字对其进行调用,而不用管是否用async修饰

    • 无async修饰符

    如果一个异步方法只是对别的异步方法进行简单调用,无太多复杂逻辑(如获取异步方法的返回值并做处理),则可以直接去掉async修饰符

    对于使用async的异步方法,编译器会负责将返回值转换为Task对象(reurn 3 自动转换为Task ),但是不带async修饰符的异步方法,需要自己自定义Task对象。

    string s1 = await ReadFileAsync(1);
    Console.WriteLine(s1);
    
    Task<string> ReadFileAsync(int num)
    {
    	switch (num)
    	{
    		case 1:
    			return File.ReadAllTextAsync("d:/1.txt"); //返回Task对象
    		case 2:
    			return File.ReadAllTextAsync("d:/2.txt");
    		default:
    			return Task.CompleterTask;//返回Task对象,需要自定义,不能直接return,因为无async修饰
                						  //public static Task CompletedTask { get; }
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    重要问题:

    1. 有异步方法,建议使用异步方法
    2. 由于某些原因,有的方法不能被async修饰,那么在方法内就不能用await来调用异步方法了,也就是不能自动将Task类型的返回值识别为T类型。对于Task返回值类型,可以使用Result属性或者GetAwaiter().GetResult方法来等待异步执行结束后获取返回值。对于Task返回值类型,可以使用Wait方法来调用异步方法并等待任务结束,但不推荐这样,会造成阻塞调用线程。
    string s1 = File.ReadAllTextAsync("./1.txt").Result;
    string s2 = File.ReadAllTextAsync("./1.txt").GetAwaiter().GetResult();
    File.ReadAllTextAsync("./1.txt").Wait();
    
    • 1
    • 2
    • 3
    1. 异步方法的暂停
    using HttpClient httpClient = new HttpClient();
    string s1 = await httpClient.GetStringAsync("https://www.ptpress.com.cn");
    await Task.Delay(3000);//不要使用Thread.sleep会阻塞调用线程
    string s2 = await httpClient.GetStringAsync("https://www.rymooc.com");
    
    • 1
    • 2
    • 3
    • 4
    1. 异步方法中如果有CancellationToken参数,则可以使用该对象提前终止操作。
    2. 可以使用Task.WhenAll同时等待多个Task的执行结束。另外也有Task.WhenAny只要有一个任务完成,代码就向下执行
    Task<string> t1 = File.ReadAllTextAsync("d:/1.txt");
    Task<string> t2 = File.ReadAllTextAsync("d:/2.txt");
    Task<string> t3 = File.ReadAllTextAsync("d:/3.txt");
    string[] results = await Task.WhenAll(t1, t2, t3);
    string s1 = results[0];
    string s2 = results[1];
    string s3 = results[2];
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1. 接口中或者抽象方法不能使用async修饰符,可以把这些方法的返回值设置为Task类型,在具体实现中可以添加async关键字修饰。
  • 相关阅读:
    一文详解Mybatis动态SQL,建议收藏
    stable diffusion webUI之赛博菩萨【秋葉】——工具包新手安裝与使用教程
    LeetCode 2558. 从数量最多的堆取走礼物
    车道线检测laneatt算法实战CULane Datasets、Tusimple数据集——安装运行训练步骤
    Java学习笔记5.3.2 Set接口 - TreeSet类
    vue 多环境文件配置(开发,测试,生产)
    我怀疑这是IDEA的BUG,但是我翻遍全网没找到证据!
    黑马JVM总结(四)
    Scrum Master证书(CSM)题目总结!
    OptiFDTD应用:用于光纤入波导耦合的硅纳米锥仿真
  • 原文地址:https://blog.csdn.net/weixin_44064908/article/details/126244599