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}");
}
}
可以看到前三次打印都是同一个线程,从 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 关键字修饰的方法。
- 异步方法的返回值一般都是
Task<T>
,T
才是真正的返回值类型 - 如果方法没有返回值,要把返回值声明为非泛型的
Task
- 调用泛型方法时,通过
await
关键字,这样可以拿到泛型指定的T类型的返回值 - 异步方法具有“传递性”,一个方法中如果有 await 调用,则这个方法也必须修饰为 async
异步方法的命名通常以 Async 结尾
async、await关键字的使用简化了多线程开发,但是async、await ≠ “多线程”
异步编程思想:不等待
async 方法的缺点
- 异步方法会生成一个类,运行效率没有普通方法高(因为通过状态机 MoveNext 的方式调用)
- 可能会占用非常多的线程(其中发生了线程切换)
非必要情况下直接返回
Task<T>
类型,不使用async关键字标记
这样只是普通方法调用,运行效率更高,不会造成线程浪费