异步编程是指在程序执行过程中,不需要等待某个操作完成,就可以继续执行后续的代码。比如我们开发了一个web页面中有一个上传文件功能,我们上传文件时使用异步操作,就不用等待文件的上传时间,可以先在网页上进行其他操作。但是如果我们的需求是等待上传文件完成之后才能进行下一步操作,比如我在boss上上传简历,然后根据附件简历生成在线简历,然后我在对在线简历进行优化,这时候就需要加一个await等待这个异步完成。
下面使用代码来举一个例子
创建一个 100000000个a 的超长字符串,然后把这个字符串写进d盘的文本里面
public static async Task Main(string[] args)
{
string str = new string('a', 100000000);
await File.WriteAllTextAsync("d:/1.txt", str);//加了await的异步写入
File.WriteAllTextAsync("d:/2.txt", str);//异步写入
File.WriteAllText("d:/3.txt", str);
}
打个断点 ,一步一步来执行
执行完第一行写入到d盘的1.txt时 ,d盘已经有了一个记事本,有很多的a,因为这句代码加了await,这里会等它执行完再到下一步。
接着往下走,执行到最后一行代码时,虽然D盘也有2.txt了,但是由于我没有加await,此时并没有等第三句代码执行完。所以这个断点往下走时我们会发现并不会像上面一样等很久,因为异步方法会立刻返回到调用者,因此也叫(非阻塞方法)
可以看到记事本中有很多的a,但是很显然没有 100000000个,很显然是写到一半被我的断点停住了。
把断点继续,此时最后两句代码一起在执行,分别往2.txt和3.txt写入。
在上面的方法中加入打印线程,看一下执行过程线程的变化
string str = new string('a', 100000000);
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
await File.WriteAllTextAsync("d:/1.txt", str);//加了await的异步写入
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
File.WriteAllTextAsync("d:/2.txt", str);//异步写入
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
File.WriteAllText("d:/3.txt", str);
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
打印结果显示,加了await的异步方法执行完后线程发生了变化 ,因为原有线程遇见await会被解放,用于执行异步任务的线程会在任务完成后返回到await处接替原有线程继续执行任务
再举一个例子,我们自己创建一个异步方法
public static async Task MethodAsync()
{
await Task.Run(() =>
{
for (int i = 0; i < 1000; i++)
{
Console.WriteLine(" MethodAsync "+i);
}
Console.WriteLine("异步线程=====>"+Thread.CurrentThread.ManagedThreadId);
});
}
再创建一个普通方法
public static void Method()
{
for (int i = 0; i < 1000; i++)
{
Console.WriteLine(" Method " +i);
}
Console.WriteLine("普通方法线程=====>"+Thread.CurrentThread.ManagedThreadId);
}
在main方法中使用await调用异步方法
public static async Task Main(string[] args)
{
Console.WriteLine("线程main =====>"+Thread.CurrentThread.ManagedThreadId);
await MethodAsync();
Console.WriteLine("线程main=====>"+Thread.CurrentThread.ManagedThreadId);
Method();
Console.WriteLine("线程main=====>"+Thread.CurrentThread.ManagedThreadId);
}
把方法停在普通方法前
控制台输出👇
因为加了await,所以等待异步方法for循环完成,从控制台输出结果看出,main方法一开始的线程是1,在执行异步方法时线程是3,执行完异步方法后回到main方法,打印线程还是3,因此上面提到的
原有线程遇见await会被解放,用于执行异步任务的线程会在任务完成后返回到await处接替原有线程继续执行任务
这句话成立。
那我们再试试不加await的
还是一样停在刚刚的断点处,可是此时的两次主线程打印都是1,异步方法还没开始打印
继续往下走,在控制台中发现,异步方法中新开了一个线程3,和普通方法交叉打印,最后打印主方法的线程也是原来的线程。
说完await,我们再来看看async
用async
来修饰一个方法,表明这个方法是异步的,声明的方法的返回类型必须为:void
或Task
或Task<TResult>
。方法内部必须含有await
修饰的方法,如果方法内部没有await
关键字修饰的表达式,哪怕函数被async
修饰也只能算作同步方法,执行的时候也是同步执行的。
因此await和async是形影不离的,如下创建两个方法
Task<string> ReadAsync()
{
return File.ReadAllTextAsync(@"D:\1.txt");
}
async Task<string> ReadAsync2()
{
return await File.ReadAllTextAsync(@"D:\1.txt");
}