概述
同步:指必须等待前一个操作完成,后续操作才能继续。同步操作会阻塞线程直到任务完成。
异步:异步操作不会阻塞线程,允许程序在等待某个任务完成的同时,继续执行其他任务。
异步编程适用场景:
1、从网络请求数据
2、访问数据库
3、读取和写入到文件系统
4、执行成本高昂的计算
好处:
通过使用异步编程,可以避免性能瓶颈并增强应用程序的总体响应能力,不会阻塞线程,增强用户体验。
规则:
1、async
关键字用于标记一个方法为异步方法
2、按照约定,异步方法的名称以“Async”后缀结尾。例如CalculateAsync
3、async
方法需要在主体中有 await
关键字,否则它们将永不暂停
4、异步方法必须返回一个 Task
或 Task<T>
,表示该方法的异步执行结果。
代码模板
async 和 await异步流程
1、使用await指定暂停点,只有等待暂停点之前的异步任务完成后才能通过该点,并执行await之后的逻辑。
2、方法体中可以包含一个或多个await运算符。
无参无返回
public async Task GetUrlContentAsync()
{
using var client = new HttpClient();
Task<string> getStringTask = client.GetStringAsync("https://learn.microsoft.com/dotnet");
DoIndependentWork();
string contents = await getStringTask;
Console.WriteLine(contents);
}
有参无返回
public async Task GetUrlContentAsync(string url)
{
using var client = new HttpClient();
Task<string> getStringTask = client.GetStringAsync(url);
DoIndependentWork();
string contents = await getStringTask;
Console.WriteLine(contents);
}
void DoIndependentWork()
{
Console.WriteLine("Working...");
}
有参有返回
public async Task<int> GetUrlContentLengthAsync(string url)
{
using var client = new HttpClient();
Task<string> getStringTask = client.GetStringAsync(url);
DoIndependentWork();
string contents = await getStringTask;
return contents.Length;
}
void DoIndependentWork()
{
Console.WriteLine("Working...");
}
Console.WriteLine("异步任务开始");
// 调用异步方法
var url = "https://learn.microsoft.com/dotnet";
var result = await test.GetUrlContentLengthAsync(url);
Console.WriteLine("异步任务完成 "+ result);
应用场景
1、游戏中,执行复杂的逻辑运算
如果在游戏中进行某个开销极大的逻辑运算,那么可能会造成游戏运行卡顿或者UI界面无法操作。 此问题的最佳解决方法是启动一个后台线程,使用 Task.Run
执行工作,并使用 await
等待其结果。 这可确保在执行工作时 UI 能流畅运行。
public async Task CalculateAsync()
{
await Task.Run(() =>
{
Console.WriteLine("计算中");
});
Console.WriteLine("计算好了");
}
Console.WriteLine("异步任务开始");
// 调用异步方法
await CalculateAsync();
Console.WriteLine("异步任务完成");
2、文件异步读写
写入文本
public async Task SimpleWriteAsync()
{
string filePath = "simple.txt";
string text = $"Hello World";
await File.WriteAllTextAsync(filePath, text);
}
读取文本
public async Task SimpleReadAsync()
{
string filePath = "simple.txt";
string text = await File.ReadAllTextAsync(filePath);
Console.WriteLine(text);
}
3、从网络提取数据
static readonly HttpClient client = new HttpClient();
public async Task SimpleWriteAsync(string url)
{
// 在try/catch块中调用异步网络方法来处理异常
try
{
//向指定的Uri发送GET请求,并以字符串形式返回响应体,异步操作
string responseBody = await client.GetStringAsync(url);
Console.WriteLine(responseBody);
}
catch (HttpRequestException e)
{
Console.WriteLine("Message :{0} ", e.Message);
}
}
Console.WriteLine("异步任务开始");
var url = "http://www.contoso.com/";
// 调用异步方法
await SimpleWriteAsync(url);
Console.WriteLine("异步任务完成");
4、等待多个任务完成
如果需要并行检索多个数据部分的情况。 Task
API 包含两种方法(即Task.WhenAll和Task.WhenAny),这些方法允许你编写在多个后台作业中执行非阻止等待的异步代码。
static readonly HttpClient client = new HttpClient();
async Task GetWebMsgAsync(string url)
{
// 在try/catch块中调用异步网络方法来处理异常
try
{
var response = await client.GetStringAsync(url);
Console.WriteLine(response);
Console.WriteLine("================================================");
}
catch (HttpRequestException e)
{
Console.WriteLine("Message :{0} ", e.Message);
}
}
public async Task GetWebDataList()
{
var taskList = new List<Task>();
var list = new List<string>()
{
"https://weibo.com/",
"https://www.bilibili.com/",
"https://blog.csdn.net/",
};
foreach (var url in list)
{
taskList.Add(GetWebMsgAsync(url));
}
await Task.WhenAll(taskList);
}
Console.WriteLine("异步任务开始");
// 调用异步方法
await GetWebDataList();
Console.WriteLine("异步任务完成");
注意
1、 I/O 绑定(例如从网络请求数据、访问数据库或读取和写入到文件系统)
CPU 绑定(例如执行成本高昂的计算)
如果你的工作为 I/O 绑定,请使用 async
和 await
(而不使用 Task.Run
)。 不应使用任务并行库。如果你的工作属于 CPU 绑定,并且重视响应能力,请使用 async
和 await
,但在另一个线程上使用 Task.Run
。
2、在 LINQ 表达式中使用异步 lambda 时请谨慎LINQ 中的 Lambda 表达式使用延迟执行,这意味着代码可能在你并不希望结束的时候停止执行。 如果编写不正确,将阻塞任务引入其中时可能很容易导致死锁。 此外,此类异步代码嵌套可能会对推断代码的执行带来更多困难。 Async 和 LINQ 的功能都十分强大,但在结合使用两者时应尽可能小心。