ASP.NET MVC中自定义任务使用了什么线程池?

4
在《Pro ASP.NET MVC 4》一书中,有一个异步操作的示例:
public class RemoteDataController : AsyncController
{
 public async Task<ActionResult> ConsumeAsyncMethod() {
    string data = await new RemoteService().GetRemoteDataAsync();
    return View("Data", (object)data);
 }
}


public class RemoteService 
{
  public async Task<string> GetRemoteDataAsync() {
    return await Task<string>.Factory.StartNew(() => {
            Thread.Sleep(2000);
            return "Hello from the other side of the world";
        });
    }
}

我的问题是:任务不仅使用线程池中用于处理请求的线程吗?
假设我有一个同步I/O绑定方法。我认为在我的操作中使用Task.Run并等待调用此方法不会导致可以同时处理的更多请求,因为I/O绑定方法的任务不再可用于请求处理。或者是否只有用于请求的单独线程池,并且在操作中使用Task.Run自动使用不同的线程池?这个问题让我思考:在高流量场景下在ASP.NET中使用ThreadPool.QueueUserWorkItem,其中答案或多或少是应仅使用来自库的异步方法,这些库使用其自己的线程池。
是否可以配置该行为?在ASP.NET WebForms中是否起作用相同?
1个回答

4

例子

这是一个非常糟糕的例子。我立刻看到了三个问题,但最主要的一个就像你指出的那样:

任务不会只使用线程池中已经用于请求服务的线程吗?

是的,这个例子会这样做。

请考虑使用以下示例代替:

public class RemoteDataController : Controller
{
  public async Task<ActionResult> ConsumeAsyncMethod() {
    string data = await new RemoteService().GetRemoteDataAsync();
    return View("Data", data);
  }
}

public class RemoteService 
{
  public async Task<string> GetRemoteDataAsync() {
    await Task.Delay(2000);
    return "Hello from the other side of the world";
  }
}

原始示例使用了 Thread.Sleep 阻塞线程池线程。这在 ASP.NET 上完全是不利的。一般来说,不要在 ASP.NET 上使用 Task.Factory.StartNewTask.Run
相反,Task.Delay 是自然异步操作。通过“自然异步”,我指的是与 I/O 操作相同的异步操作(例如 web 调用的 HttpClient)。自然异步操作不使用线程,因此对 ASP.NET 服务器非常有吸引力(减少对线程池的压力,使您可以进行更多的扩展)。
思考一下这个操作是如何工作的非常有趣:当您像我的示例中使用自然异步方法时,一个线程启动请求直到它遇到 await;此时,请求线程被返回到线程池 (!),在接下来的两秒钟内没有线程处理该请求(但请求尚未完成)。我喜欢称这种现象为“零线程并发性”。当 Delay 完成时,一个线程恢复处理请求并将其完成。
顺便说一下,AsyncController 是 MVC3 中的剩余物。使用 async/await 不需要它。

我相信你在这里是有意地简化,但我认为暗示没有使用线程有点危险。没有使用线程池线程,但根据正在进行的工作,系统上可能会使用其他线程。具体来说,Task.Delay永远不需要线程,但还有其他可能需要的东西。然而,从Web应用程序的角度来看,是“无线程”的工作,因此并没有错,只是不完全准确。 - Chris Pratt
@ChrisPratt: 我的意思是该请求未使用任何线程。系统(和应用程序)中其他地方有许多线程正在进行各种操作,如果阻止请求线程,那些相同的线程仍将继续执行相同的操作。即使是 Task.Delay :BCL实际上有一个“计时器线程”,负责所有托管计时器,因此计时器有一个BCL线程。 但这只是AppDomain中的一个线程,而不是每个计时器。 这里的原则相同:我们避免了每个请求都需要额外线程的情况。 - Stephen Cleary

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接