async、await原理

async的方法会被C#编译器编译成一个类,会根据 await 调用切分为多个状态。对 async 方法的调用会被拆分为对 MoveNext 的调用。和状态机有关,还没有理清楚

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}");
    }
}

async await axios 用法_后端


可以看到前三次打印都是同一个线程,从 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 await axios 用法_后端_02


可以看到,同样是标记 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关键字标记
这样只是普通方法调用,运行效率更高,不会造成线程浪费