ASP.NET 和异步 - 它是如何工作的?

ASP.NET and async - how it works?

我知道这是一个常见的问题,但我已经阅读了大量文章并感到困惑。现在我认为最好不要阅读它们))。

那么,ASP.NET 是如何工作的(仅关于线程):

  1. http 请求由线程池中的线程处理。
  2. 当请求正在处理时,此线程正忙,因为请求正在此线程内处理。
  3. 当请求处理完成后,线程returns到线程池,服务器发送响应。

这种描述的行为是否正确?

当我在 ASP.NET MVC 控制器中启动新任务时到底发生了什么?

public ActionResult Index()
{
    var task1 = Task.Factory.StartNew(() => DoSomeHeavyWork());
    return View();
}

private static async Task DoSomeHeavyWork()
{
    await Task.Delay(5000);
}
  1. 控制器动作开始在处理当前请求的线程内执行 - T1。
  2. 线程池为task1再分配一个线程(T2)。
  3. task1 在 T2 内开始 "immediately"。
  4. 查看结果returns"immediately".
  5. ASP.NET 做一些工作,服务器发送响应,T1 returns 到线程池,T2 还活着。
  6. 在 DoSomeHeavyWork 完成一段时间后,T2 线程将返回到线程池。

是否正确?

现在让我们看看异步操作

public async Task<ActionResult> Index()
{
    await DoSomeHeavyWork();
    return View();
}

,我了解与之前代码示例的区别,但不了解流程,在此示例中的行为如下:

  1. 操作开始在处理当前请求的线程内执行 - T1。
  2. DoSomeHeavyWork "immediately" returns一个任务,我们也叫它"task1"吧。
  3. T1 returns 到线程池。
  4. DoSomeHeavyWork 完成后,Index 操作继续执行。
  5. 索引操作执行后,服务器将发送响应。

请解释第 2 点和第 5 点之间发生了什么,问题是:

  1. DoSomeHeavyWork 是在 task1 中处理还是在何处处理("awaited")?我认为这是一个关键问题。
  2. 等待后哪个线程将继续处理请求 - 线程池中的任何新线程,对吗?
  3. 请求产生从线程池分配的线程,但在 DoSomeHeavyWorkAsync 完成之前不会发送响应,并且此方法在哪个线程中执行无关紧要。换句话说,根据 single 请求和 single 具体任务 (DoSomeHeavyWork),使用异步没有任何好处。正确吗?
  4. 如果前面的说法是正确的,那么我不明白异步如何提高具有相同 单个 任务的多个请求的性能。我会尽力解释。假设线程池有 50 个线程可用于处理请求。单个请求应该至少由线程池中的一个线程处理,如果请求启动另一个线程,那么所有线程都将从线程池中获取,例如request 需要一个线程来处理自己,并行启动 5 个不同的任务并等待所有任务,线程池将有 50 - 1 - 5 = 44 个空闲线程来处理传入的请求 - 所以这是一种并行性,我们可以提高单个任务的性能请求,但我们减少了可以处理的请求数量。因此,根据 ASP.NET 中的请求处理,我认为只有以某种方式启动 IO 完成线程的任务才能实现异步 (TAP) 的目标。但是在这种情况下,IO 完成线程如何回调线程池线程?

Is this described behaviour right ?

是的。

Is it correct ?

是的。

is the DoSomeHeavyWork processed inside task1 or where (where it is "awaited") ? I think this a key question.

从当前代码来看,DoSomeHeavyWork 将异步等待 Task.Delay 完成。是的,这将发生在线程池分配的同一个线程上,它不会旋转任何新线程。但是不能保证它会是 相同的线程

which thread will continue to process the request after await?

因为我们谈论的是 ASP.NET,那将是一个任意的线程池线程,HttpContext 编组到它上面。如果这是 WinForms 或 WPF 应用程序,你会在 await 之后再次点击 UI 线程,前提是你不使用 ConfigureAwait(false).

request produces thread allocating from the thread pool, but response will not be sent until the DoSomeHeavyWorkAsync finished and it doesn't matter in which thread this method executes. In other words, according to single request and single concrete task (DoSomeHeavyWork) there is no benefits of using async. Is it correct ?

在这种特殊情况下,您将看不到异步的好处。当您有并发请求访问服务器时,async 会大放异彩,并且其中很多请求都在进行 IO 绑定工作。例如,在访问数据库时使用异步时,您可以在查询执行的时间内释放线程池线程,从而允许同一线程同时处理更多请求。

But how IO completion thread calls back thread pool thread in this case ?

你必须将并行性和并发性分开。如果您需要计算能力来并行执行 CPU 绑定工作,异步不是实现它的工具。另一方面,如果你有很多 concurrent IO 绑定操作,比如访问数据库进行 CRUD 操作,你可以通过 释放线程从异步的使用中获益而 IO 操作正在执行。这是异步的主要关键点。

线程池有专用的 IO 完成线程池,以及工作线程,您可以通过调用 ThreadPool.GetAvailableThreads 查看它们。当您使用 IO 绑定操作时,检索回调的线程通常是 IO 完成线程,而不是工作线程。他们都有不同的游泳池。