在异步方法中使用Task.Run()会导致线程池饥饿问题吗?

4

我在我的.NET Core应用程序中有这段代码:

[HttpPost]
[Route("doSomething")]
public async Task<IActionResult> DoSomethingAsync([FromBody] Input input)
{
    // Do Something
    var task1 = Task.Run(async () => 
    {
        await taskFactory.DoTask(input);
    });

    // Do Something Differently
    var task2 = Task.Run(async () => 
    {
        await taskFactory.DoAnotherTask(input);
    });

    await Task.WhenAll(task1, task2);

    return Accepted();
}

DoTask()DoAnotherTask()都是彼此独立的,可以并行执行,但必须等待它们都处于完成状态。

因此,我创建了两个任务,并使用Task.WhenAll()等待它们。

但我收到了一条评论,指出在异步方法中不要使用Task.Run(),因为这可能会导致线程池饥饿。

问题1:我的代码如何导致线程池饥饿?
问题2:如果它导致线程池饥饿,我该如何并行运行这两个任务?


你为什么在这里使用 Task.Run 而不是直接从 DoTask 返回任务? - DavidG
1
请注意,您不需要await来自Task.Run的结果,而是捕获任务以在Task.WhenAll中使用,您也可以对DoTaskDoAnotherTask的结果执行相同操作。 - juharr
@DavidG 是的,那是可以避免的。这是不必要的。但我只是想了解它是如何导致线程池饥饿的。 - Code-47
唯一需要将这些调用包装在 Task.Run 中的原因是,如果它们在执行任何 async 代码之前有 CPU 绑定的代码。 - juharr
线程池有限数量的可用线程。Task.Run将工作排队在线程池上运行,因此如果过度使用,可能会耗尽所有线程。 - Johnathan Barclay
显示剩余3条评论
2个回答

3
要有信心回答您的问题,我们必须了解DoTaskDoAnotherTask方法的实现。如果我们不知道它们的实现方式,我们只能假设它们已经被正确地实现并且遵循异步方法的礼节,即立即返回Task,而不会阻塞调用线程。在此假设下,答案是:不,您的代码不会导致线程池饥饿。这是因为Task.Run所使用的ThreadPool线程的工作量非常小,仅仅是创建一个Task对象,因此它几乎可以立即返回到ThreadPool中。
需要指出的是,虽然将行为良好的异步委托封装在Task.Run中对线程池的健康状况影响微乎其微,但也没有任何好处。请看这个半相关的问题:在ASP .NET MVC Web应用程序中,Task.Run被认为是不好的实践吗?

2

针对你的线程池饥饿问题,如果你运行一个已经是异步的任务,并使用task.run执行2个新的任务,在其中运行2个异步方法,每次调用你将有5个任务,然后等待两个任务完成,你将达到每个请求6个任务。

我通常会像这样做,你仍然有4个任务,但最终线程池会持续更长时间。

[HttpPost]
[Route("doSomething")]
public async Task<IActionResult> DoSomethingAsync([FromBody] Input input)
{
    // Do Something
    var t1 = taskFactory.DoTask(input);

    // Do Something Differently
    var t2 = taskFactory.DoAnotherTask(input);


    await Task.WhenAll(t1, t2);

    return Accepted();
}


3
可能这是更好的方法,但并没有回答原帖中关于线程池饥饿的问题,就像他的代码审查者告诉他们的那样。 - pinkfloydx33

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