.net core 非阻塞的异步编程 及其 运行机制
本文主要分为三个部分:1、语法格式
2、线程调度情况
3、编程注意事项
* 阅读提示 :鼠标悬停在 章节标题 上可见 文章目录
异步编程(Task Asynchronous Programming,TAP),一种编程模式(Task-based Asynchronous Pattern)。
TAP 是 .NET 中推荐的异步编程模式,基于 Task 和 Task 类型,用于表示异步。
异步编程一般应对两种场景,一是 I/O 绑定,当需要网络连接(连接数据库或读写到文件系统等)等耗时长的任务;二是 CPU 绑定,需要耗时长的计算。
1、简单的语法格式
.net 一直在为开发人员简化和标准化异步的写法,从 .net 4.5 开始就已经支持使用 aysnc 和 await 的关键字。
异步的语法格式如下:
private async Task DoSomeStuffAsync(..)
{
..
await ..
..
}
l 关键词 async 本身不具备什么意义,只是装饰,当方法冠以 async 关键词,方法体内允许使用 await
l await 是标记需要等待的地方,但其本质并非阻塞线程。
l “非阻止操作”:指当运行到 await 时,会把当前线程返回到上一级调用者继续执行,如果没有上一级调用者,则该线程当场释放。
非阻止操作
阻止操作
备注
任一任务完成
await Task.WhenAny
Task.WaitAny
非阻塞:线程遇到 await 时会返回上一层调用者继续执行,如果没有上一级调用者,则释放该线程;
阻塞:线程在等待期间不能执行其他任务,也不释放线程,硬等
所有任务完成
await Task.WhenAll
Task.WaitAll
等待一段时间
await Task.Delay
Thread.Sleep
l 异步方法返回的值总是 Task 的实例,可以是 Task 类型或 Task,其中 TResult 是执行的方法的返回类型
l 如果直接拿异步方法的结果,形如 var obj = GetSomethingAsync(),这个 obj 是一个 Task 对象,其中 obj.AsyncStatus,obj.Result 可见执行情况
l await + 执行异步方法,形如 await GetSomethingAsync() 会得到 TResult 实例
l 直接使用 task.Result 得到的任务结果其状态是未知的,应该使用 await task,保证任务是 Completed 的
l 一般地,异步方法的名字需要添加后缀 Async,以便于写代码的时候区分开同步方法和异步方法
l 等待异步任务的执行过程中,如果其中发生了错误,该异步任务的外层 try catch 会捕捉到,它也是一种任务结果
2、异步运行机制,线程调度
观察以下代码,思考一下控制台会输出什么?
public static async Task Main(string[] args)
{
logMessage("Main <<----");
var task = GetUrlContentLengthAsync();
logMessage("Main ---->>");
await task;
logMessage("ALL COMPLETED");
}
static async Task<int> GetUrlContentLengthAsync()
{
logMessage("GetUrlContentLengthAsync start ");
using var client = new HttpClient();
Task<string> getStringTask = client.GetStringAsync("https://learn.microsoft.com/dotnet");
// Do some independent work..
var contents = await getStringTask;
logMessage("GetUrlContentLengthAsync end ");
return contents.Length;
}
static void logMessage(string msg)
{
Console.WriteLine($"{DateTime.Now.ToString("MM-dd HH:mm:ss.fff")} [{Thread.CurrentThread.ManagedThreadId}] {msg}");
}
打印结果:
11-18 18:34:34.563 Main <<----
11-18 18:34:34.632 GetUrlContentLengthAsync start
11-18 18:34:34.810 Main ---->>
11-18 18:34:37.283 GetUrlContentLengthAsync end
11-18 18:34:37.286 ALL COMPLETED
先分析一下 GetUrlContentLengthAsync 这个异步方法,简单归纳会存在以下步骤:
[*]httpClient.GetStringAsync 发起 HTTP 请求,并立即返回一个未完成的任务。
[*]await 关键字会暂停 GetStringAsync 方法的执行,并将控制权返回给调用方。
[*]任务调度器通过操作系统的通知机制来监听 HTTP 请求的响应。
[*]操作系统在后台监控 I/O 操作的状态,并在操作完成时通知应用程序。
[*]任务调度器随后选择一个可用的线程来继续执行 异步方法的剩余部分(await 之后)。
线程运行过程如下:
* HTTP 请求是一个 I/O 操作,操作系统会通过 I/O 完成端口(IOCP)来处理这些操作。
* IOCP 是一种高效的机制,用于处理异步 I/O 操作。它允许操作系统在 I/O 操作完成时通知应用程序。
3、注意事项
l 如果调用了 异步方法,一定要 await 任务执行结果
反面示例:
public static async Task Main(string[] args)
{
logMessage("Main <<----");
// fault example: if do not wait the result then we will do not know what happened on it
GetSomeStuff();
logMessage("Main ---->>");
}这个异步任务已经在执行,但是却没有后续处理,不知道是成功或是失败,又或者一直在执行没有办法停止,这是危险的。
l 如果要使用 task.Result ,此前一定要 await
await task 得到的是 completed async status 的 result,而直接使用 task.Result 的方式拿结果并不保证这个任务是完成的。
l 是否线程安全
异步编程的机制,允许到正在处理一个请求时,同时存在多个线程在处理操作,如果在对同一个对象做写入操作,这是危险的。
所以并行时的任务最好是没有关系的。
如果使用锁,需要考虑是否会导致死锁的问题。
l 异步编程可能会增加代码复杂度,慎重取舍
异步编程并不一定带来性能提升,毕竟上下文切换也是有开销的,对于简单的任务可能一条线做完的方式更合适。
Reference:
The Task Asynchronous Programming (TAP) model with async and await" - C# | Microsoft Learn
Asynchronous programming scenarios - C# | Microsoft Learn
Asynchronous programming - C# | Microsoft Learn
来源:https://www.cnblogs.com/carmen-019/p/18555115
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!
页:
[1]