02020203 .NET Core重难点知识03-异步编程、编写异步方法、同步方法和异步方法调用异步方法、异步委托
02020203 .NET Core重难点知识03-异步编程、编写异步方法、同步方法和异步方法调用异步方法、异步委托
1. 图片美化服务例子(视频Part2-3)
- 服务器能够同时服务的请求数量有限
void BeautifyPic(File photo, Resopnse response)
{
byte[] bytes = 美化图片(photo);
response.Write(bytes);
}
说明:
1. 不等美化图片完成,又可以接收另外一个请求。
2. 异步编程记住“不等待”。后续慢慢理解。
2. 异步编程概述
-
异步编程并不能提高单个请求处理时间,而是可以同时处理多个请求。
- 假设一个图片必须要处理5S,异步编程可以实现同时处理多个图片,但是每个图片的处理时间可能是6S。
-
异步编程关键字:async、await。
- 异步编程并不是多线程,传统多线程开发太麻烦,容易死锁,崩溃等,异步编程可以简化多线程开发,不用管所,线程同步等问题。(后续会讲到)
3. 异步方法(视频Part2-4)
-
通常来说用async关键字修饰的方法是异步方法,如果没用async修饰的也可能是异步方法(后续讲解)。
- 异步方法的返回值一般是Task
,T是真正的返回值类型,Task 。惯例:异步方法名字以Async结尾。
- 异步方法的返回值一般是Task
-
调用异步方法时,一般在方法前面加上await关,这样拿到的返回值就是泛型指定的T类型。
-
不以Async结尾在语法上可行,但是建议大家用上,这样一看就是异步方法。
-
如果没有Async结尾不一定就不是异步方法,我们看别人代码的时候要善于区分。
-
总结:自己写的建议带上Async,看别人写得不带Async的时候要能看出来。
3.1 没有返回值的异步方法
// 没有返回值的异步方法
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.WaitLine(s);
}
说明:
1. 即使异步方法没有返回值,也最好把返回值申明为非泛型的Task。
2. 异步方法是可以没有返回值的(即用void修饰),但是void修饰的异步方法容易出问题。
3.2 异步方法的传染性
- 一个方法中如果有await调用,则这个方法也必须修饰为async。
// 1. 错误写法示例
async Task<T> T1Async()
{
}
Task<T> T2Async() // 错误写法,因为T1Async是异步方法,此时T2Async方法里面调用了T1Async方法,需要用async修饰为异步方法。
{
await T1Async();
}
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// 2. 正确写法
async Task<T> T1Async()
{
}
async Task<T> T2Async() // 用async修饰的异步方法。
{
await T1Async();
}
async Task<T> T3Async() // T3Async方法使用了T2Async方法,那么也必用async修饰。
{
await T2Async();
}
3.3 同步方法演示
using System;
using System.IO;
namespace Chapter0202Exp01
{
class Program
{
static void Main(string[] args)
{
string fileName = @"..\创建记事本文件.txt";
File.WriteAllText(fileName, "同步方法写入:Hello QinWay!");
string txtContent = File.ReadAllText(fileName);
Console.WriteLine(txtContent);
}
}
}
控制台输出:
同步方法写入:Hello QinWay!
- 注意,上述代码段执行之后在本地磁盘会创建一个“01 同步方法创建的记事本.txt”文件。

3.4 异步方法演示
using System;
using System.IO;
using System.Threading.Tasks;
namespace Chapter0202Exp02
{
class Program
{
// 注意此时Main方法使用了异步方法,也要用async修饰,并且返回值为Task
static async Task Main(string[] args)
{
string fileName = @"..\创建记事本文件.txt";
await File.WriteAllTextAsync(fileName, "异步方法写入:Hello QinWay!"); // @1
string txtContent = await File.ReadAllTextAsync(fileName); // @2
Console.WriteLine(txtContent);
}
}
}
控制台输出:
异步方法写入:Hello QinWay!
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// 如果在@1处,如下没有写await,此时有波浪线警告。
File.WriteAllTextAsync(fileName, "异步方法写入:Hello QinWay!");
波浪线警告 → CS4014:由于此调用不会等待,因此在此调用完成之前将会继续执行当前方法,请考虑将“await”运算符应用于调用结果。
说明:
1. 此时虽然有波浪线警告,但是我们运行程序任然可以从控制台输出:异步方法写入:Hello QinWay!。这是因为我们写入的内容太少了,在我们执行@2这条语句之前,已经将@1处的文件写完了。
2. 本质上不写await,程序在启动写入后会一直保持写入状态,直到所有内容写入完毕才停止。
3.5 await的用法
// 通过写入复杂的内容来测试不写await的结果
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
namespace Chapter0202Exp02
{
class Program
{
// 注意此时Main方法使用了异步方法,也要用async修饰,并且返回值为Task
static async Task Main(string[] args)
{
string fileName = @"..\创建记事本文件.txt";
StringBuilder strBuilder = new StringBuilder();
for(int i=0; i<10000; i++)
{
strBuilder.AppendLine("异步方法循环写入10000次Hello QinWay");
}
File.WriteAllTextAsync(fileName, strBuilder.ToString()); // 此处不加await
string txtContent = await File.ReadAllTextAsync(fileName);
Console.WriteLine(txtContent);
}
}
}
报错:System.IO.IOException:“The process cannot access the file 'E:...\创建记事本文件.txt' because it is being used by another process.”
说明:此时"创建记事本文件.txt"正在循环写入内容,这个.txt文件被占用在,然后我们又来读取这个.txt文件,表示进程被占用。
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// 使用await后,可以不等写完就开始执行。
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
namespace Chapter0202Exp02
{
class Program
{
// 注意此时Main方法使用了异步方法,也要用async修饰,并且返回值为Task
static async Task Main(string[] args)
{
string fileName = @"..\创建记事本文件.txt";
StringBuilder strBuilder = new StringBuilder();
for(int i=0; i<10000; i++)
{
strBuilder.AppendLine("异步方法循环写入10000次Hello QinWay");
}
await File.WriteAllTextAsync(fileName, strBuilder.ToString()); // @1
string txtContent = await File.ReadAllTextAsync(fileName); // @2
Console.WriteLine(txtContent);
}
}
}
控制台输出:
异步方法循环写入10000次Hello QinWay
异步方法循环写入10000次Hello QinWay
...
异步方法循环写入10000次Hello QinWay
说明:在@2处使用了await之后,可以等@1处写完,然后开始读文件,此时注意输出的“异步方法循环写入10000次Hello QinWay”的内容。
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
综上所述:
1. 如果不写await,在执行一个异步方法A的时候,这个异步方法A会一直执行完成。此时一个方法B如果要使用A的返回值,可能出现A还没执行完成,B就已经在使用,此时程序会报错。
2. 如果写上await,如果B要使用A的返回值,会一直等待A执行完成,然后再使用。
4. 编写异步方法
- 如果同样的功能,既有同步方法,又有异步方法,那么首先使用异步方法。
- .NET5中,很多框架的方法也都支持异步,比如:Main,WinForm事件处理函数。
using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
namespace Demo02
{
class Program
{
static async Task Main(string[] args)
{
await DownloadHtmlAsync01("https://www.baidu.com", @"..\不带返回值下载网站的html.txt");
Console.WriteLine("不带返回值的方法下载html完成,并保存成功!");
int htmlLength = await DownloadHtmlAsync02("https://www.hao123.com", @"..\带返回值下载网站的html.txt");
Console.WriteLine($"带返回值的方法下载html完成,并保存成功!其html文件字符数为:{htmlLength}");
Console.ReadLine();
}
/// <summary>
/// 1. 不带返回值的异步方法。
/// 2. HttpClient实现了IDisposable接口,要使用using进行资源回收。
/// </summary>
static async Task DownloadHtmlAsync01(string url, string fileName)
{
using (HttpClient httpClient = new HttpClient())
{
string html = await httpClient.GetStringAsync(url);
await File.WriteAllTextAsync(fileName, html);
}
}
/// <summary>
/// 1. 带返回值的异步方法。
/// </summary>
static async Task<int> DownloadHtmlAsync02(string url, string fileName)
{
using (HttpClient httpClient = new HttpClient())
{
string html = await httpClient.GetStringAsync(url);
await File.WriteAllTextAsync(fileName, html);
return html.Length;
}
}
}
}
控制台输出:
不带返回值的方法下载html完成,并保存成功!
带返回值的方法下载html完成,并保存成功!其html文件字符数为:501457
5. 同步方法调用异步方法(不推荐使用)
- 对于不支持的异步方法怎么办?
- 首先我们考虑是否有其它技术实现,不支持异步的话说明比较Low。
- 当必须要用时,可以用如下形式来实现。
- Wait() → 无返回值
- Result() → 有返回值,但是有死锁的风险,尽量不用。
// @1 假设Main方法不支持异步调用,此时非异步的Main方法调用异步的ReadAllBytesAsync方法。
using System;
using System.IO;
using System.Threading.Tasks;
namespace Demo02
{
class Program
{
static void Main(string[] args) // 同步方法
{
// @1 同步方法调用无返回值的异步方法
Task tw = File.WriteAllTextAsync(@"..\text01.txt", "abcdefg");
tw.Wait();
// @2 同步方法调用有返回值的异步方法
Task<string> tr = File.ReadAllTextAsync(@"..\text01.txt");
string s = tr.Result;
Console.WriteLine(s.Substring(0,5));
Console.ReadLine();
}
}
}
控制台输出
abcde
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
注意:在@1处,使用await调用时,编译器会完成如下替换
await File.WriteAllTextAsync(@"..\text01.txt", "abcdefg")
<== 替换为 ==>
Task tw = File.WriteAllTextAsync(@"..\text01.txt", "abcdefg");
tw.Wait();
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
注意,在@2处,使用await调用时,编译器会完成如下替换
string s = await File.ReadAllBytesAsync(@"..\text01.txt");
<== 替换为 ==>
Task tw = File.WriteAllTextAsync(@"..\text01.txt", "abcdefg");
tw.Wait();
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
总结:
1. 使用await调用,编译器自动的为我们完成了替换,并自动接收了tw或tr的的值,然后为我们将值完成替换。
1.1 如果是无返回值,自动的使用Wati()方法。
1.2 如果有返回值,自动替换为Result属性。
2. 我们可以采用@1处、@2处手动的形式,将值拿出来,并且完成替换。
6. 异步委托
- 有时候需要在委托里面调用异步方法。
// 普通委托
using System;
using System.IO;
using System.Threading;
namespace Demo02
{
class Program
{
static void Main(string[] args)
{
ThreadPool.QueueUserWorkItem((obj) =>
{
while (true)
{
Console.WriteLine("使用委托!");
}
});
Console.Read(); // 守住线程等待。
}
}
}
控制台输出:
使用委托!
使用委托!
...
说明:ThreadPool是线程池,在.NET里面一直有这样一个类。在子线程中要执行的代码可以用QueueUserWorkItem()将其扔到线程池里面。
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// 异步委托
using System;
using System.IO;
using System.Threading;
namespace Demo02
{
class Program
{
static void Main(string[] args)
{
ThreadPool.QueueUserWorkItem(async (obj) => // 异步Lambda表达式。
{
while (true)
{
await File.WriteAllTextAsync(@"..\异步Lambda表达式.txt", "abcdefg");
Console.WriteLine("实现异步委托!");
}
});
Console.Read(); // 守住线程等待。
}
}
}
说明:如果要在Lambda表达式中使用异步方法,可以用async关键字将Lambda表达式修饰为异步的。
结尾
书籍:ASP.NET Core技术内幕与项目实战
视频:https://www.bilibili.com/video/BV1pK41137He
著:杨中科
ISBN:978-7-115-58657-5
版次:第1版
发行:人民邮电出版社
※敬请购买正版书籍,侵删请联系85863947@qq.com※
※本文章为看书或查阅资料而总结的笔记,仅供参考,如有错误请留言指正,谢谢!※