.Net 异步编程之async和await

async、await原理

async的方法会被C#编译器编译成一个类,会根据 await 调用切分为多个状态。对 async 方法的调用会被拆分为对 MoveNext 的调用。和状态机有关,还没有理清楚,先看其他人的解密吧:解密async await 原理(.net)

await 看似“等待”,但经过编译后,其实没有“wait”

为什么说 await 没有等待呢?

因为 await 调用“等待”期间,.Net 会把当前的线程返回给线程池,等异步方法调用执行完毕后,框架会从线程池再取出一个线程执行后续的代码(PS:造成了线程切换)。

// *** Main *** /
int threadId = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine($"第一次打印:{threadId}");  // 打印当前线程 Id

await AsyncProgramming.TestAsync();

threadId = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine($"第五次打印:{threadId}");

// *** AsyncProgramming *** /
internal class AsyncProgramming
{
    public static async Task TestAsync()
    {
        int threadId = Thread.CurrentThread.ManagedThreadId;
        System.Console.WriteLine($"第二次打印:{threadId}");

        string path = @"D:\Temp";
        string fileName = "1.txt";
        string fullPath = Path.Combine(path, fileName);

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 10000; i++)
        {
            sb.Append("aaaa");
        }

        threadId = Thread.CurrentThread.ManagedThreadId;
        System.Console.WriteLine($"第三次打印:{threadId}");

        #region 耗时任务

        await File.WriteAllTextAsync(fullPath, sb.ToString());

        #endregion

        threadId = Thread.CurrentThread.ManagedThreadId;
        System.Console.WriteLine($"第四次打印:{threadId}");
    }
}

在这里插入图片描述
可以看到前三次打印都是同一个线程,从 await 等待异步方法执行后面两次打印的是一个线程。由此可见,调用 await 执行语句确实把当前的线程给释放了(返回给线程池),调用完毕则又重新取出一个线程执行后续的代码(线程进行切换了)。

异步方法≠多线程

异步方法的代码并不会自动在新线程中执行,除非把代码放到新线程中执行。修改上面代码中关于写的部分:

internal class AsyncProgramming
{
    public static async Task TestAsync()
    {
        int threadId = Thread.CurrentThread.ManagedThreadId;
        System.Console.WriteLine($"第二次打印:{threadId}");

        string path = @"D:\Temp";
        string fileName = "1.txt";
        string fullPath = Path.Combine(path, fileName);

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 10000; i++)
        {
            sb.Append("aaaa");
        }

        threadId = Thread.CurrentThread.ManagedThreadId;
        System.Console.WriteLine($"第三次打印:{threadId}");

        File.WriteAllText(fullPath, sb.ToString());  // 修改的部分

        threadId = Thread.CurrentThread.ManagedThreadId;
        System.Console.WriteLine($"第四次打印:{threadId}");
    }
}

执行结果:
在这里插入图片描述
可以看到,同样是标记 async 的异步方法,但并没有创建新的线程,而是在同一个线程中执行。继续修改:

internal class AsyncProgramming
{
    public static async Task TestAsync()
    {
        int threadId = Thread.CurrentThread.ManagedThreadId;
        System.Console.WriteLine($"第二次打印:{threadId}");

        string path = @"D:\Temp";
        string fileName = "1.txt";
        string fullPath = Path.Combine(path, fileName);

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 10000; i++)
        {
            sb.Append("aaaa");
        }

        threadId = Thread.CurrentThread.ManagedThreadId;
        System.Console.WriteLine($"第三次打印:{threadId}");

        // 修改部分
        await Task.Run(() =>
            {
                File.WriteAllText(fullPath, sb.ToString());
            });

        threadId = Thread.CurrentThread.ManagedThreadId;
        System.Console.WriteLine($"第四次打印:{threadId}");
    }
}

我们通过Task.Run的方式创建了一个新的线程,在线程中执行了写的代码。由此可见,异步方法的却不等于多线程,只能说异步方法可能是多线程的

异步方法

异步方法:使用 async 关键字修饰的方法

  1. 异步方法的返回值一般都是Task<T>T才是真正的返回值类型
  2. 如果方法没有返回值,要把返回值声明为非泛型的Task
  3. 调用泛型方法时,通过await关键字,这样可以拿到泛型指定的T类型的返回值
  4. 异步方法具有“传递性”,一个方法中如果有 await 调用,则这个方法也必须修饰为 async

异步方法的命名通常以 Async 结尾

async、await关键字的使用简化了多线程开发,但是async、await ≠ “多线程”

异步编程思想:不等待

async 方法的缺点
  1. 异步方法会生成一个类,运行效率没有普通方法高(因为通过状态机 MoveNext 的方式调用)
  2. 可能会占用非常多的线程(其中发生了线程切换)

非必要情况下直接返回Task<T>类型,不使用async关键字标记
这样只是普通方法调用,运行效率更高,不会造成线程浪费

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Qanx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值