C# 用async和await表达异步

本文详细介绍了C#中的async和await关键字用于简化异步编程的原理和用法。通过实例展示了await不会创建新线程,而是利用Task类在后台执行任务,主线程在遇到await后不会阻塞,而是继续执行后续代码。同时,阐述了await如何将异步方法的剩余部分作为回调附加到子线程,以及如何处理嵌套的异步调用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

async和await

为了简化异步编程,摒弃复杂的流控制,提高异步编程的可读性,C# 5引入了async和await关键字。它们的实现基于Task类,将异步操作变为一种语言特性。使得异步变得像同步一样易读。

用法

async:

  • 声明异步方法
  • 使用await,需要用async修饰await所在的方法

await:

  • 等待异步方法执行完毕但不会阻塞主线程
  • 将async方法分为两部分,await前的为async方法执行的主线程,await之后的部分在子线程中执行
//模拟一个异步操作
//我做饭,发现没盐了,叫老婆去买盐
//我(主线程)继续做饭,老婆(子线程)去买盐
static void Main(string[] args)
{
    ShowCurrentThreadID($"CookThread");
    // 调用异步方法,主线程会进入该方法
    Task<string> saltStatu = BuySaltAsync();
	// 主线程继续做饭
    Console.WriteLine("Cooking..");
    // 如果用到异步方法的返回结果,阻塞在这里,等待异步执行完毕,获得有效返回值
    if (saltStatu.Result == "Salt is ready")
        Console.WriteLine("Add Salt");
    Console.WriteLine("A feast is ready!");
    Console.ReadKey();//防止程序直接退出
}
static async Task<string> BuySaltAsync()
{
    ShowCurrentThreadID("CallForWife");
    // 叫老婆去买盐,自己回去继续做饭(创建一个子线程)
    string res = await Task<string>.Run(() =>
    {
        ShowCurrentThreadID("   WifeShopping");
        Thread.Sleep(100);
        return "Salt is ready";
    });    
    ShowCurrentThreadID("   SaltGoHome");
    // 盐买回家,更新Flag
    return res;
}
// 获得当前线程ID
static void ShowCurrentThreadID(string threadTag)
{
    Console.WriteLine($"{threadTag} ID is {Thread.CurrentThread.ManagedThreadId.ToString()}");
}

运行结果:
在这里插入图片描述
上面的例子看起来似乎是await创建了一个子线程,但并不是,实际上是Task.Run()创建了一个子线程。在它们之间加点延时,把异步方法修改下:

static async Task<string> BuySaltAsync()
        {
            ShowCurrentThreadID("CallForWife");
            Task<string> task = Task<string>.Run(() =>
            {
                ShowCurrentThreadID("   WifeShopping");
                Thread.Sleep(100);
                return "Salt is ready";
            });
            Thread.Sleep(50);
            ShowCurrentThreadID("   WaitForWife");
            string res = await task;
            ShowCurrentThreadID("   SaltGoHome");
            return res;
        }

运行结果:
在这里插入图片描述
Task.Run()创建了一个子线程,并且主线程遇到await才退出异步方法。

那如果在子线程完成才使用await会发生什么?我们把延时加到200ms,如下

static async Task<string> BuySaltAsync()
{
    ShowCurrentThreadID("CallForWife");
    Task<string> task = Task<string>.Run(() =>
    {
        ShowCurrentThreadID("   WifeShopping");
        Thread.Sleep(100);
        return "Salt is ready";
    });
    Thread.Sleep(200);
    ShowCurrentThreadID("   WaitForWife");
    string res = await task;            
    ShowCurrentThreadID("   SaltGoHome");
    return res;
}

运行结果:
在这里插入图片描述
现在连异步方法的后半部分即await后面的代码也由主线程执行了。这说明,在子线程执行过程中,await会把后面的部分作为“回调函数”给子线程附加,此例中子线程已经结束被回收,就没法再在子线程中运行回调。

await等待的是Task任务执行的结果:一个Task类型的返回值,那可不可以再等待一个返回值为Task的异步方法呢?试试:

static async Task<string> BuySaltAsync()
{
    ShowCurrentThreadID("CallForWife");   
    // 老婆不想去买,叫儿子去 
    string res = await CallSonBuySaltAsync();
    ShowCurrentThreadID("   SaltGoHome");
    return res;
}
static async Task<string> CallSonBuySaltAsync()
{
    ShowCurrentThreadID("CallForSon");
    string res = await Task<string>.Run(() =>
    {
        ShowCurrentThreadID("   SonShopping");
        Thread.Sleep(100);
        return "Salt is ready";
    });
    return res;
}

运行结果:
在这里插入图片描述
在上述嵌套await中,主线程遇到第一个await并没有返回,而是继续往下执行嵌套的异步方法,在这个子线程创建后,主线程执行到await,await通知主线程已经有一个子线程了,主线程才退出异步方法。

小结

  • await等待异步方法执行完毕且不会阻塞主线程,await不创建线程,

  • await会把异步方法后面的部分作为“回调函数”给子线程附加

  • await将异步方法的返回值Task拆包为T类型变量

  • 如果子线程有返回值,并且在主线程中被调用,主线程会阻塞在返回值的调用部分

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值