.net 非壅闭的异步编程 及 线程调度过程

打印 上一主题 下一主题

主题 1006|帖子 1006|积分 3018

本文主要分为三个部分:
1、语法格式
2、线程调度环境
3、编程注意事项
4、练一练
* 阅读提示 :鼠标悬停在 章节标题 上可见 文章目录
 
 
异步编程(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、异步运行机制,线程调度

观察以下代码,思考一下控制台会输出什么?
  1.     public static async Task Main(string[] args)
  2.     {
  3.         logMessage("Main <<----");
  4.         var task = GetUrlContentLengthAsync();
  5.         logMessage("Main ---->>");
  6.         await task;
  7.         logMessage("ALL COMPLETED");
  8.     }
  9.     static async Task<int> GetUrlContentLengthAsync()
  10.     {
  11.         logMessage("GetUrlContentLengthAsync start ");
  12.         using var client = new HttpClient();
  13.         Task<string> getStringTask = client.GetStringAsync("https://learn.microsoft.com/dotnet");
  14.         // Do some independent work..
  15.         var contents = await getStringTask;
  16.         logMessage("GetUrlContentLengthAsync end ");
  17.         return contents.Length;
  18.     }
  19.     static void logMessage(string msg)
  20.     {
  21.         Console.WriteLine($"{DateTime.Now.ToString("MM-dd HH:mm:ss.fff")} [{Thread.CurrentThread.ManagedThreadId}] {msg}");
  22.     }
复制代码
 
打印效果:
  1. 11-18 18:34:34.563 [1] Main <<----
  2. 11-18 18:34:34.632 [1] GetUrlContentLengthAsync start
  3. 11-18 18:34:34.810 [1] Main ---->>
  4. 11-18 18:34:37.283 [7] GetUrlContentLengthAsync end
  5. 11-18 18:34:37.286 [7] ALL COMPLETED
复制代码
 
先分析一下 GetUrlContentLengthAsync 这个异步方法,简朴归纳会存在以下步骤:

  • httpClient.GetStringAsync 发起 HTTP 请求,并立即返回一个未完成的使命。
  • await 关键字会暂停 GetStringAsync 方法的实行,并将控制权返回给调用方。
  • 使命调度器通过操作系统的通知机制来监听 HTTP 请求的响应。
  • 操作系统在背景监控 I/O 操作的状态,并在操作完成时通知应用程序。
  • 使命调度器随后选择一个可用的线程来继承实行 异步方法的剩余部分(await 之后)。
 
线程运行过程如下:

 
* HTTP 请求是一个 I/O 操作,操作系统会通过 I/O 完成端口(IOCP)来处理这些操作。
* IOCP 是一种高效的机制,用于处理异步 I/O 操作。它允许操作系统在 I/O 操作完成时通知应用程序。
 
 
 
3、注意事项

如果调用了 异步方法,一定要 await 使命实行效果
反面示例:
  1.     public static async Task Main(string[] args)
  2.     {
  3.         logMessage("Main <<----");
  4.         // fault example: if do not wait the result then we will do not know what happened on it
  5.         GetSomeStuff();
  6.         logMessage("Main ---->>");
  7.     }
复制代码
这个异步使命已经在实行,但是却没有后续处理,不知道是乐成或是失败,又大概一直在实行没有办法停止,这是危险的。
 
如果要使用 task.Result ,此前一定要 await
await task 得到的是 completed async status 的 result,而直接使用 task.Result 的方式拿效果并不包管这个使命是完成的。
 
是否线程安全
异步编程的机制,允许到正在处理一个请求时,同时存在多个线程在处理操作,如果在对同一个对象做写入操作,这是危险的。
以是并行时的使命最好是没有关系的。
如果使用锁,需要考虑是否会导致死锁的题目。
 
异步编程可能会增加代码复杂度,慎重弃取
异步编程并不一定带来性能提升,毕竟上下文切换也是有开销的,对于简朴的使命可能一条线做完的方式更符合。
 
 
 
 
4、练一练

以下代码有什么题目?
  1.         public async Task<ResultResponse> ValidateReceiptAsync(List<Guid> customerIds, ReceiptRequest request, CancellationToken cancellationToken)
  2.         {
  3.             var tokenTask = _medicalCheckServiceHelper.GetServiceToken(cancellationToken);
  4.             
  5.             var medicalProvidersTask = _medicalProviderRepo.GetMedicalProvidersNames();
  6.             var customersTask = _customerService.GetByIdsAsync(customerIds);
  7.             
  8.             var checkResults = await Task.WhenAll(request.UuidList.Select(uuid => _medicalCheckServiceHelper.GetResultAsync(uuid, tokenTask.Result, cancellationToken)));
  9.             return await ConsolidateReceiptsResult(medicalProvidersTask.Result, checkResults, await customersTask);
  10.         }
复制代码
 
 
 
 
 
 
 
 
 
 
 
 
 
 
思考一下
 
 
 
 
 
 
 
 
 
 
 
 
 
 

  • 1. tokenTask.Result 在使命完成之前访问会导致壅闭,应该在 await Task.WhenAll 之后再访问。
使用 await,而制止使用 xxTask.Result
壅闭线程:如果使命尚未完成,访问 Result 会壅闭当火线程,直到使命完成。这会导致性能题目,特别是在 UI 线程中使用时,会导致界面卡顿。
死锁风险:在某些环境下,特别是在同步上下文(如 UI 线程)中,访问 Result 可能会导致死锁(使命等待当火线程释放,而当火线程又在等待使命完成)。
异常处理:直接访问 Result 可能会忽略使掷中的异常。使用 await 可以更好地处理异常。
 

  • 2.        确认哪些可以并行处理,注意线程安全
这里有 4 个使命,分别是 获取 token(假作 taskA)、获取治疗厂商(taskB)、获取用户信息(taskC)、获取检查效果(taskD)
此中,taskD 依靠于 taskA ;如今需要确定 taskA、taskB、taskC 之间的依靠关系。
 



    • a.        假设真实场景如下:

      • i.        taskA 取自于配置中心,也即它会走 HTTP 请求
      • ii. taskD 是一个第三方接口,也即一个 HTTP 请求
      • iii.        taskB 和 taskC 取自同一个数据库,并且它们共用了一个数据库毗连的上下文


那么可能的修改是这样的:
  1. public async Task<ResultResponse> ValidateReceiptAsync(List<string> customerIds, ReceiptRequest request, CancellationToken cancellationToken)
  2. {
  3.     var tokenTask = _medicalCheckServiceHelper.GetServiceToken(cancellationToken);
  4.     var medicalProvidersTask = _medicalProviderRepo.GetMedicalProvidersNames();
  5.     var token = await tokenTask;
  6.     var checkResults = await Task.WhenAll(request.UuidList.Select(uuid => _medicalCheckServiceHelper.GetResultAsync(uuid, token, cancellationToken)));
  7.    
  8.     var medicalProviders = await medicalProvidersTask;
  9.     var customers = await _customerService.GetByIdsAsync(customerIds);
  10.     return await ConsolidateReceiptsResult(medicalProviders, checkResults, customers);
  11. }
复制代码
 
 



    • b. 假设真实场景如下:

      • i. taskA 取自于配置中心,也即它会走 HTTP 请求
      • ii. taskD 是一个第三方接口,也即一个 HTTP 请求
      • iii. taskB 和 taskC 取自差别数据库或差别的 HTTP 请求,它们相互独立


那么可能的修改是这样的:
  1. public async Task<ResultResponse> ValidateReceiptAsync(List<string> customerIds, ReceiptRequest request, CancellationToken cancellationToken)
  2. {
  3.     var tokenTask = _medicalCheckServiceHelper.GetServiceToken(cancellationToken);
  4.     var medicalProvidersTask = _medicalProviderRepo.GetMedicalProvidersNames();
  5.     var customersTask = _customerService.GetByIdsAsync(customerIds);
  6.     var token = await tokenTask;
  7.     var checkResultsTask = Task.WhenAll(request.UuidList.Select(uuid => _medicalCheckServiceHelper.GetResultAsync(uuid, token, cancellationToken)));
  8.     var medicalProviders = await medicalProvidersTask;
  9.     var customers = await customersTask;
  10.     var checkResults = await checkResultsTask;
  11.     return await ConsolidateReceiptsResult(medicalProviders, checkResults, customers);
  12. }
复制代码
 
但其实并不尽完善,由于这种写法的可读性并没有那么好,看起来更寻求资源优化。
以是说引入了 异步编程 的话,代码是会变复杂的,每写一步都需要慎重考虑。
 
 
 
 
 
 
Reference:
[1] The Task Asynchronous Programming (TAP) model with async and await" - C# | Microsoft Learn
[2] Asynchronous programming scenarios - C# | Microsoft Learn
[3] Asynchronous programming - C# | Microsoft Learn
 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

北冰洋以北

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表