C# async/await与异步方法

异步的概念

参考:https://learn.microsoft.com/zh-cn/dotnet/csharp/asynchronous-programming/
以做早餐的场景为例,现在有以下几个需要做步骤:

  1. 倒一杯咖啡
  2. 热锅,然后煎两个鸡蛋
  3. 煎三片培根
  4. 烤两片面包
  5. 在吐司中加入黄油和果酱
  6. 倒入一杯橙汁

在具体执行中,我们不需要等待上一件事完成才开始做另一件事(这是同步的方法)。
开始热锅的时候,可以去开始煎培根,开始煎培根,可以开始烤面包;煎培根(第三个任务)不需要等待热锅,两个鸡蛋都煎好(等待第二个任务完成)才开始,烤面包(第四个任务)也不需要等待培根煎好(等待第三个任务完成)后才开始。(这是异步的方法)

放在编程中:
当一个方法被调用,调用者需要等待该方法执行完毕并返回才能继续执行,这个方法是同步方法。
当一个方法被调用立即返回,并获取一个线程执行该方法内部的业务,调用者不用等待该方法执行完毕并返回,而是继续执行后续代码,这个方法是异步方法。
如果方法里含有异步操作,那么整个方法都是异步的。

异步的实现

async/await 的使用

一个异步操作被抽象为Task, 若有返回值则是Task<T>

async Task DoAsync()
{

}
async Task Caller()
{
    await DoAsync();
}

async是一个专门给编译器的提示,意思是该函数的实现可能会出现await

await意为等待,需要等待await后面的函数运行完并且有返回结果后,才继续执行后续代码。异步方法使用await来指定一个暂停点。

异步编程不一定涉及多线程,而是利用异步任务的等待和非阻塞特性来提高程序的并发性。

以官方文档给出的示例为例:

public async Task<int> GetUrlContentLengthAsync()
{
    var client = new HttpClient();

    Task<string> getStringTask = client.GetStringAsync("https://learn.microsoft.com/dotnet");

    DoIndependentWork();

    string contents = await getStringTask;

    return contents.Length;
}

void DoIndependentWork()
{
    Console.WriteLine("Working...");
}

GetUrlContentLengthAsync()async修饰符,返回类型为Task<int>
contents = await getStringTask;这一行代码,表示等待getStringTask执行完毕,并且有返回结果后,才继续执行GetUrlContentLengthAsync()内部的后续代码。(等待,指定的一个暂停点)
getStringTask完成之前,控件返回至GetUrlContentLengthAsync的调用方。这样不会阻塞执行GetUrlContentLengthAsync的线程,该线程可以被释放用来执行其他工作。
getStringTask完成时,控件将在此继续。

回到开头做早餐的例子:4,5 可以合并到一起形成一个Task

static async Task<Toast> MakeToastWithButterAndJamAsync(int number)
{
    var toast = await ToastBreadAsync(number);
    ApplyButter(toast);
    ApplyJam(toast);

    return toast;
}

var toast = await ToastBreadAsync(number);一个厨师开始烤面包,并等待面包烤好。在等待过程中,当前的厨师(该线程)可以做其他事情。比如,回到调用方,去煎蛋。

以下是 https://learn.microsoft.com/zh-cn/dotnet/csharp/asynchronous-programming/ 给出的最终代码运行的一种结果

Pouring coffee
coffee is ready
Warming the egg pan...
putting 3 slices of bacon in the pan
cooking first side of bacon...
Putting a slice of bread in the toaster
Putting a slice of bread in the toaster
Start toasting...
cracking 2 eggs
cooking the eggs ...
flipping a slice of bacon
flipping a slice of bacon
flipping a slice of bacon
cooking the second side of bacon...
Remove toast from toaster
Putting butter on the toast
Putting jam on the toast
toast is ready
Put bacon on plate
Put eggs on plate
bacon is ready
eggs are ready
Pouring orange juice
oj is ready
Breakfast is ready!

可以看到Start toasting...进程没有一直等待,在Remove toast from toaster之前,回到调用方做了其他事情

关于async标识
  • async标识会告诉编译器这个方法里面可能会用到await关键字来标识该方法是异步的,这样,编译器将会在状态机中编译此方法。该方法执行到await关键字时会处于挂起状态,直到该异步操作完成后才恢复继续执行后续操作。
  • 当将方法用async标识时且返回值为void或者Task或者Task<TReuslt>,此时该方法会在当前线程中一直同步执行。
  • 标记async,仅仅是异步的一个必要不充分条件
使用ConfigureAwait(false)来避免死锁
public async Task<string> GetStringAsync()
{
    // 在某个给定的线程上进入这个方法
    // funcA()

    await SomeAsyncMethod().ConfigureAwait(false);

    // 可能在与开始时 funA() 不用的线程上执行 
    // funcB()
}

这允许代码继续执行在任何可用的线程上,确保不捕获同步上下文并防止潜在的死锁。

参考文档:

  1. https://learn.microsoft.com/zh-cn/dotnet/csharp/asynchronous-programming/task-asynchronous-programming-model
  2. https://www.limfx.pro/readarticle/128/shen-du-li-jie-cyi-bu-bian-cheng-
  3. https://www.cnblogs.com/xiaoxiaotank/p/13529413.html
  4. https://zhuanlan.zhihu.com/p/546961514
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值