关于c#中异步async和await的理解

之前给大家介绍了所谓异步编程的用法,但是没有细致的理解到,今天想和大家一起探讨一下;

前文: 

C#笔记14 异步编程Async,await,task类-CSDN博客

异步的起初

应用程序会启动一个进程,一个进程可以有很多线程。

如果一个线程,我们给他命名为主线程。

这个线程如果一直往下走,只有一条线,这就是单线程。

还是这个线程,如果它运行的途中创建了新的小伙伴去执行其他任务,这就是多线程。

现在我们的主线程正在做着自己的事情类似于展示一个界面之类的,此时创建一个线程去后台执行其他任务,主线程不去管它,这就是异步。

所以多线程的起初就是异步的。

同步的出现

现在,主线程创建了两个小弟,两个小弟功能差不多,这两个小弟为了抢占一个资源,打起来了。这可不行对吧,于是我们就设立各种同步机制和同步锁,控制他们学会等待

等待的方式各不相同,但是毫无疑问,线程间互相有了约束和阻塞。

到这里其实我们想做的事情已经基本完成了。

线程各司其职遇到资源就进行同步机制的等待和唤醒操作。

但是还是会有问题,比如多线程的管理,小弟太多了,根本管不过来,小弟之间使用资源的调度原则不够公平等等。当然了,这要求程序员的管理和操作系统的调度配合。

除此之外也发现管理多线程的方式不够先进:

管理的困难

主线程是老大,今天派小弟出去办事,小弟办事之后,确实办完了,但是主线程不知道小弟已经办完了,这时候是不是有小伙伴要想到C#中的委托和事件了?

这样就可以让小弟通知老大哥自己做好了,老大哥订阅了小弟的这个事件,这样就能在小弟完成事件的时候执行对应的方法了。

但是每一个小弟都这样,老大哥要手动管理这些事件,就好像老大哥每天都得坐在办公室听汇报了一样,尤其是管理线程的我们觉得很烦,这个组织太杂乱了!

尤其是什么情况?需要反复派出小弟去干活的时候,比如密集的io环境,我们还要和每个小老弟都约定一个事件,啥时候通知老大哥。

尤其是线程一多,老大哥虽然不迷糊但是老大哥也很烦,但是我们程序员,也就是负责真正管理小弟的人迷糊了。这可怎么办?

此时:

如果不派小弟去干,自己干,老大哥自己的事情就做不好了。(阻塞)

如果派出小弟,让小弟自己干自己的,老大又不知道到底干的什么情况了,就算小弟已经把结果搞出来了,已经通过什么形式提交了也不知道。(多线程异步)

如果手动告诉小弟怎么通知老大,这下倒是可以了,但是咱们秘书(程序员)可怜了,天天就负责管理每个小弟,告诉小弟在哪告诉老大,然后记下老大小弟给的数据怎么处理,用什么方法处理。(事件通知的多线程)

到这我们秘书就想到一个方法减轻自己的工作量。将社团改组,大哥和小弟都不是按所谓线程来做事了,而是新的机制或者说不同的任务处理者Task。

新的处理异步的方法

async和await出场!

async:用于标记一个方法是异步的,告诉编译器这个方法会有异步操作。该方法仍然可以在线程池中调度执行任务,但这个并不由 async 本身控制。
await:用于等待异步操作完成,而不会阻塞当前线程。它只会让出控制权,让当前线程去做其他事情,当操作完成时,再继续执行后续代码。

async标记一个事情,告诉老大和小弟,这个事情是异步的,这个事情要让小弟去干,不能影响老大哥工作。所以这个方法使用之后会立刻返回一个任务的引用作为结果,等到事情办完了这个结果才会被填充为指定的值。

await专门用来调用一个需要耗时间的操作,调用之后会让出控制权,等到再次获取控制权也就是后面等待的操作有结果的时候,如果异步方法有返回值的话,await还会自动把值从结果里拿出来给指定的对象。

老大哥调用异步方法就像派出一个小弟,task作为方法的返回值就像小弟给大哥留的纸条,等到小弟任务完成了,这个纸条上就会出现结果。这中间呢纸条就放在这里,。

await是逻辑上的等待这个结果,如果这个结果没有出现,后面的逻辑就不会执行。

意思是老大虽然后面有些事要等待这个事情,不代表老大就抛下自己手上的事情了,实际上老大还是在干活的。

你会发现小弟中间也会有一些await操作。

老大哥一调用这个事情啊,可以使用task,意思就是老大哥派出了这个小弟,但是自己没有去干奥,这个小东西就是小弟留给老大的小纸条,

await就是小弟告诉老大:“好,现在该到这里了,我得等某个任务完成了才能继续。

意思是什么?小弟办事也不是说立刻就办完了。也就是我们的方法内部也许还有耗时间的更具体的操作,比如老大让小弟读写一批文件,小弟可能要一个个文件读写,这就是小弟给老大的纸条。

await 就像说:“等这个任务做完了,再通知我,我再继续往下走。”

所以,await 的作用是让程序在某个任务完成之前,不阻塞其他操作。它并不会傻傻等着,而是会继续处理其他的事情,等到这个耗时操作完成后,再回到你指定的位置继续执行。

using System;
using System.Net.Http;
using System.Threading.Tasks;

class Program
{
    static async Task Main(string[] args)
    {
        Console.WriteLine($"{DateTime.Now:HH:mm:ss} - 大哥:开始做事,我让小弟去下载文件");

        // 大哥派小弟去干下载文件的活
        Task downloadTask = 小弟去下载文件Async("http://example.com/largefile");

        // 大哥自己继续干活
        for (int i = 0; i < 10; i++)
        {
            Console.WriteLine($"{DateTime.Now:HH:mm:ss} - 大哥:我在忙别的事... {i}");
            await Task.Delay(1000);  // 模拟大哥处理别的事情需要一些时间
        }

        // 等小弟把下载任务做完
        await downloadTask;

        Console.WriteLine($"{DateTime.Now:HH:mm:ss} - 大哥:小弟终于把文件下载完了,事情都做完了,收工!");
    }

    // 小弟去下载文件,过程可能很久
    static async Task 小弟去下载文件Async(string url)
    {
        Console.WriteLine($"{DateTime.Now:HH:mm:ss} - 小弟:开始下载文件...");

        using (HttpClient client = new HttpClient())
        {
            // 模拟下载文件的耗时操作
            await Task.Delay(5000); // 假装下载需要5秒钟
            Console.WriteLine($"{DateTime.Now:HH:mm:ss} - 小弟:文件下载完了!");
        }
    }
}

10:00:00 - 大哥:开始做事,我让小弟去下载文件
10:00:00 - 小弟:开始下载文件...
10:00:00 - 大哥:我在忙别的事... 0
10:00:01 - 大哥:我在忙别的事... 1
10:00:02 - 大哥:我在忙别的事... 2
10:00:03 - 大哥:我在忙别的事... 3
10:00:04 - 大哥:我在忙别的事... 4
10:00:05 - 小弟:文件下载完了!
10:00:05 - 大哥:我在忙别的事... 5
10:00:06 - 大哥:我在忙别的事... 6
10:00:07 - 大哥:我在忙别的事... 7
10:00:08 - 大哥:我在忙别的事... 8
10:00:09 - 大哥:我在忙别的事... 9
10:00:09 - 大哥:小弟终于把文件下载完了,事情都做完了,收工!

可以看出来,大哥在派出小弟之后自己还在做事,事实上小弟此时也正在下载文件。(注意,这里的小弟不是某个特定的我们管理的线程,而是被我们的系统指派的线程池甚至就是操作系统在做。)

大哥的await就是等小弟。

小弟的await就是小弟等小弟。

注意,await不会让后面的代码提前执行,反而是会就像顺序执行一样来执行。

但是它这种等待和我们之前线程的等待又不太一样,因为这种等待是代码让出控制权给其他方法去执行。

异步方法之间的依赖

小弟之间也存在依赖的时候呢,就可以像下面一样了。

using System;
using System.Threading.Tasks;

class Program
{
    static async Task Main(string[] args)
    {
        Console.WriteLine($"{DateTime.Now:HH:mm:ss} - 老大哥:开始派小弟A去下载文件");

        // 小弟A开始下载文件,小弟B等他下载完
        Task 小弟A的任务 = 小弟A去下载文件Async();
        Task 小弟B的任务 = 小弟B等小弟A再处理文件Async(小弟A的任务);

        Console.WriteLine($"{DateTime.Now:HH:mm:ss} - 老大哥:我先干点其他的事...");

        // 等两个任务都完成
        await Task.WhenAll(小弟A的任务, 小弟B的任务);

        Console.WriteLine($"{DateTime.Now:HH:mm:ss} - 老大哥:小弟们都完成了,我收工!");
    }

    static async Task 小弟A去下载文件Async()
    {
        Console.WriteLine($"{DateTime.Now:HH:mm:ss} - 小弟A:开始下载文件...");
        await Task.Delay(3000);  // 模拟耗时的下载过程
        Console.WriteLine($"{DateTime.Now:HH:mm:ss} - 小弟A:文件下载完了!");
    }

    static async Task 小弟B等小弟A再处理文件Async(Task 小弟A的任务)
    {
        Console.WriteLine($"{DateTime.Now:HH:mm:ss} - 小弟B:我得等小弟A下载完才能干活...");
        await 小弟A的任务;  // 等待小弟A的任务完成
        Console.WriteLine($"{DateTime.Now:HH:mm:ss} - 小弟B:现在小弟A下载完了,我开始处理文件...");
        await Task.Delay(2000);  // 模拟文件处理时间
        Console.WriteLine($"{DateTime.Now:HH:mm:ss} - 小弟B:文件处理完了!");
    }
}

事实上,任务机制可以让我们像写同步代码一样来写一些需要异步的操作。

同样的代码逻辑,实际的执行上就会不同。

这和传统线程管理的方式不同,避免了管理线程和事件订阅以及那种嵌套回调函数的麻烦。

这只是当下我浅薄的理解,如果我说的有错,请帮我指出,感谢。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值