深入使用 Async/Await/Task 关键字

作者 : 慕源网 本文共2428个字,预计阅读时间需要7分钟 发布时间: 2021-12-29 共367人阅读

现代系统通过多处理器变得更加强大。同样,现代服务器应用程序开发也得到了改进,可以利用主机 CPU 的能力并行处理多个请求,以实现更大的可扩展性。

早些时候,我们对异步调用有很多限制,并且没有使用完整的 CPU 内核。.NET Framework 4 中引入的任务并行库 (TPL) 对我们的基础设施异步开发和利用进行了快速更改。

尽管如此,大多数开发人员对理解这些关键字并不是很熟悉,并且在使用过程中经常会犯错误。在这里,我们将更专注于了解这些关键字如何与异步调用一起使用。最后,我们将看到一些常规使用所需的建议。

每个 .NET/.NET Core 应用程序都与一个线程池相关联,并且是有效执行应用程序异步调用的线程集合。线程池中的这些线程称为工作线程。

线程池将用作 .NET 运行时的软件级线程管理系统。它是所有应用程序请求的排队机制。

当应用程序启动时,.NET 运行时将产生工作线程并将它们作为可用线程保留在线程池队列中。一旦有任何请求到来,运行时就会从这个队列中挑选一个工作线程并将这个传入的请求分配给它,一旦这个请求过程完成,它就会保留在队列中。运行时将产生的线程数取决于默认情况下主机系统上可用的逻辑处理器数量。如果需要,我们可以在应用程序启动时使用以下语句来增加要生成的最少工作线程。

ThreadPool.SetMinThreads(100, 100);

第一个参数是需要创建的工作线程数,第二个参数定义完成端口线程数。

让我们在这里通过以下示例代码块详细了解 Async、Await 和 Task 关键字。

public async Task<decimal> TotalGiftBudget()
{
  decimal totalBudgetAmount = 0;
  decimal percentageAmount = GetGiftPercentage();
  List<Employee> lstEmployees = new List<Employee>();
  //Fetch all active employees from Hyderabad Branch
  List<Employee> lstHyd = await GetHyderabadEmployeesListAsync();
  lstEmployees.AddRange(lstHyd);
  //Fetch all active employees from Banglore Branch
  List<Employee> lstBglre = await GetBangloreEmployeesListAsync();
  lstEmployees.AddRange(lstBglre);
  foreach(var emp in lstEmployees)
  {
    totalBudgetAmount += (emp.Salary * percentageAmount) / 100.0m;
  }
  return totalBudgetAmount;
}

假设一家公司决定用他们月薪的一定比例向所有员工支付圣诞礼物。在向员工宣布之前,他们要检查此奖金金额的预算。这种异步方法将通过异步获取海得拉巴和班加罗尔分行的所有员工并根据每个员工的工资计算预期的礼物金额来提供此信息。

调用此方法时,将在运行时发生以下操作。

当运行时找到 async 关键字时,它会在 RAM 上分配一些内存来存储方法的状态信息。

接下来,Runtime 会将这个方法分为三个部分,如下所示

一旦它执行或处理 part-1 并发现异步语句从海得拉巴分支获取员工,然后立即创建一个 I/O 线程并返回 指向其 IOCP的 Task对象,当前工作线程将保留给可用工作线程线程队列,即线程池,以便运行时将利用此工作线程来服务其他请求。

一旦海得拉巴列表准备就绪(例如,它可能从外部 API 获取),我们的完成端口会将其保留在其队列中。使用 await 关键字,以便运行时将使用GetQueuedCompletionStatus 函数不断获取 I/O 进程的状态, 一旦准备就绪,将分配一个新的可用工作线程以继续其余操作。这个新的工作线程将从 RAM 中获取状态机信息并继续该方法的剩余过程。

在我们的例子中,一旦 Hyderabad 的列表可用并添加到 listEmployee 列表中,并且运行时再次发现另一个异步语句并重复与上面相同的操作。一旦我们也得到了班加罗尔列表,就没有其他异步语句了,这次运行时将完成接下来的动作来计算总礼物金额并成功返回。

这里的兴趣点是,在我们获得来自海得拉巴和班加罗尔的员工列表之前,工作线程会被保留并用于服务其他请求。在同步调用的情况下,工作线程将被阻塞,直到它返回预算金额。以下是这些使用 async/await 关键字的异步调用的优点,可提高应用程序的性能并使其更具可扩展性。

建议
如果可能,请避免使用 Async 关键字并仅使用 Task。当我们使用Async时,一些内存会分配给Ram,这会影响性能。例如,下面的代码不需要任何异步和等待。

publc Task<int> GetSum(int a, int b)
{
    int c = a + b;
   return c;
}

万一,如果我们有一个非异步方法,我们需要调用一个异步方法。以下语句非常糟糕,它们会阻塞线程并导致性能问题。

Task.Result; //bad
Task.Wait(); //bad
Task.GetAwaiter().GetResult(); //bad

相反,我们可以像下面这样写

Public string Getsomething()
{
   var task1 = Do1Async();
   var task2 = Do2Async();

   Task.WaitAll(new[] {task1, task2});
  //or
   Task.WhenAll(new[] {task1, task2});
}
Public async Task<string> do1Async()
{}
Public async Task<string> do2Async()
{}

希望本文对您了解异步世界中 Async、Await 和 Task 关键字的最佳用法有所帮助。


慕源网 » 深入使用 Async/Await/Task 关键字

常见问题FAQ

程序仅供学习研究,请勿用于非法用途,不得违反国家法律,否则后果自负,一切法律责任与本站无关。
请仔细阅读以上条款再购买,拍下即代表同意条款并遵守约定,谢谢大家支持理解!

发表评论

开通VIP 享更多特权,建议使用QQ登录