【问题标题】:ASP.NET and async - how it works?ASP.NET 和异步 - 它是如何工作的?
【发布时间】:2015-05-22 17:16:42
【问题描述】:

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

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

  1. http 请求由线程池中的线程提供服务。
  2. 当请求正在处理时,这个线程很忙,因为请求正在这个线程内处理。
  3. 请求处理完成后,线程返回线程池,服务器发送响应。

这种描述的行为正确吗?

当我在 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 内“立即”启动。
  4. 查看结果“立即”返回。
  5. ASP.NET 做一些工作,服务器发送响应,T1 返回线程池,T2 还活着。
  6. DoSomeHeavyWork 完成一段时间后,T2 线程将返回线程池。

对吗?

现在让我们看看异步操作

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

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

  1. 动作开始在处理当前请求的线程内执行 - T1。
  2. DoSomeHeavyWork “立即”返回一个任务,我们也称它为“task1”。
  3. T1 返回线程池。
  4. DoSomeHeavyWork 完成后,Index 操作继续执行。
  5. 执行索引操作后,服务器将发送响应。

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

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

【问题讨论】:

  • await 这个问题的一些有价值的答案。

标签: asp.net asp.net-mvc async-await task-parallel-library


【解决方案1】:

这种描述的行为正确吗?

是的。

对吗?

是的。

是在 task1 内处理的 DoSomeHeavyWork 还是在哪里(它在哪里 “等待”)?我认为这是一个关键问题。

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

await之后哪个线程会继续处理请求?

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

请求产生从线程池分配的线程,但响应 在 DoSomeHeavyWorkAsync 完成并且它之前不会发送 这个方法在哪个线程中执行并不重要。换句话说, 根据单个请求和单个具体任务(DoSomeHeavyWork) 使用异步没有任何好处。对吗?

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

但是这个IO完成线程是如何回调线程池线程的 案例?

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

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

【讨论】:

  • 谢谢,很清楚。还有一个问题 - 如果我使用第三方库并且它具有名为例如的方法SomeMethodAsync 是可等待的,那么我应该用 await 关键字包装它还是直接调用 SomeMethodAsync(...).Result?其实我不知道它是否使用了任何IO操作。
  • @SamousPrime 你shouldn't block on async code,它会导致你遇到各种各样的问题,包括死锁。使用await。如果您不确定代码在做什么,您可以随时使用 .NET 反编译器查看它。
  • 关于死锁的有用示例。但是“异步”是指完全异步关键字还是异步的一般含义?我问是因为以下示例没有死锁public ActionResult Index() { int t = Job().Result; return View("Index"); } private static Task&lt;int&gt; Job() { return Task.Run(() =&gt; Task.Delay(2000).ContinueWith(t =&gt; 1)); }...
  • ... 但是public ActionResult Index() { int t = Job().Result; return View("Index"); } private static async Task&lt;int&gt; Job() { return await Task.Run(() =&gt; Task.Delay(2000).ContinueWith(t =&gt; 1)); } 有。
猜你喜欢
  • 1970-01-01
  • 2016-02-01
  • 2018-04-14
  • 2018-02-27
  • 2017-07-09
  • 2012-04-23
  • 1970-01-01
  • 2017-05-18
  • 2019-02-25
相关资源
最近更新 更多