用async关键字修饰的方法,即为“异步方法”。以下几点需要注意:
-
异步方法的返回值一般是
Task<T>,T是真正的返回值类型,如Task<int>。惯例:异步方法名字以Async结尾。 -
即使方法没有返回值,也最好把返回值声明为非泛型的Task。
-
调用泛型方法时,一般在方法前加上await,这样拿到的返回值就是泛型指定的T类型。
-
异步方法的“传染性”:一个方法中如果有await调用,则这个方法也必须修饰为async。
static async Task Main(string[] args)
{
string fileName = "d:/1.txt";
File.Delete(fileName);
File.WriteAllTextAsync(fileName, "hello async");
string s = await File.ReadAllTextAsync(fileName);
Console.WriteLine(s);
}
为什么要在调用方法前加上await?
下面我们上代码,看看加不加await有什么区别。
//异步(不加await)
string fileName = "G:/1.txt";
File.WriteAllTextAsync(fileName, "Hello!");
//Task<string> t = File.ReadAllTextAsync(fileName);
//string a = await t;
//推荐这种写法
string a = await File.ReadAllTextAsync(fileName);
Console.WriteLine(a);
运行结果:
//异步(加await)
string fileName = "G:/1.txt";
await File.WriteAllTextAsync(fileName, "Hello!");
//Task<string> t = File.ReadAllTextAsync(fileName);
//string a = await t;
//推荐这种写法
string a = await File.ReadAllTextAsync(fileName);
Console.WriteLine(a);
运行结果:
都可以正常编译运行,看起来没什么不同。下面我们换个大字符串试试。
//异步,写入大字符串(不加await)
using System.Text;
string fileName = "G:/2.txt";
StringBuilder t = new StringBuilder();
for (int i = 0; i < 10000; i++)
{
t.AppendLine("Hello!");
}
File.WriteAllTextAsync(fileName, t.ToString());
string a = await File.ReadAllTextAsync(fileName);
Console.WriteLine(a);
运行结果:
//异步,写入大字符串(不加await)
using System.Text;
string fileName = "G:/软件开发/.net 6/.NET6_杨中科/练习/Part2-4/2.txt";
StringBuilder t = new StringBuilder();
for (int i = 0; i < 10000; i++)
{
t.AppendLine("Hello!");
}
await File.WriteAllTextAsync(fileName, t.ToString());
string a = await File.ReadAllTextAsync(fileName);
Console.WriteLine(a);
运行结果:
写入大字符串时,如果不加await,会抛出异常。原因就是,写入方法还没有执行完,就往下执行了读取方法。加上await,会等写入方法执行结束后,再往下执行读取。
结论: 调用泛型异步方法时,要在方法前加上await。