在ASP.Net Core中使用Task.Run

6

我阅读了以下文章:

ASP.NET Core性能最佳实践

在Asp.Net Core中什么时候应该使用Task.Run?

在ASP.NET Core控制器中允许使用Task.Run吗?

docs.microsoft的第一篇文章中,我们可以看到这个声明:

如果有异步API可用,请异步调用数据访问、I/O和长时间运行的操作API。 不要使用Task.Run将同步API转换为异步形式。

我对这个声明不太理解。我对何时以及何处应该使用Task.Run和何时以及何处应该(能够)编写异步Web API感到困惑。例如,考虑以下方法:

public int DoSomeHeavyWork()
{
   // Consider heavy computations
   int i = 10;
   i = i * 2;
   i = i + 4;
   return i;
}

并且这种方法:

public void SaveSomthingInDb(Order ord)
{
    _dbContext.Orders.Add(ord);
    _dbContext.SaveChange();
}

根据上述声明,我应该同步编写Web方法,因为没有async版本:

public IActionResult API_DoSomeHeavyWork()
{
    return Ok(DoSomeHeavyWork());
}

但对于第二种方法,我可以这样更改:

public async Task SaveSomthingInDbAsync(Order ord)
{
    _dbContext.Orders.Add(ord);
    await _dbContext.SaveChangeAsync();
}

那么我可以像这样为它编写一个API:

public async Task<IActionResult> API_SaveSomthingInDbAsync(Order ord)
{
    await SaveSomthingInDbAsync(ord);
    return Ok("Object Added");
}

我不知道我的看法是否正确?这些方法是正确的吗?有没有办法运行第一个 API async

谢谢。


编辑 1)

感谢 Stephen Cleary。

如果我们假设这个句子: 我们在 Web API 的 Web 方法中使用 async 方法来防止阻塞线程池线程并处理更多请求 ...

请考虑以下代码:

await _context.Order.ToListAsync();

根据此代码,有一个名为 Microsoft.EntityFrameworkCore 的程序集,并且它有一个叫做 ToListAsyncasync 方法。
public static Task<List<TSource>> ToListAsync<TSource>(this IQueryable<TSource> source, CancellationToken cancellationToken = default(CancellationToken))

首先的问题是 ToListAsync 是如何实现的?它是否像这样实现?

public static Task<List<TSource>> ToListAsync<TSource>(...)
{
    Task.Run(()=>{ <Some thing>.ToList() });
}

第二个问题是,如果将方法 DoSomeHeavyWork 放在一个单独的程序集中,并按以下方式重写它:
public Task<int> DoSomeHeavyWork()
{
   Task.Run(() => 
   {
       // Consider heavy computations
       int i = 10;
       i = i * 2;
       i = i + 4;
       return i;
   }
}

我能否从我的 Web API 中调用 async 并释放线程池线程呢?

谢谢。


5
“如何使CPU密集型操作不消耗CPU(至少在可见/可测量的范围内)”这类问题总是很有趣... 当你很可能无法控制此类请求的流入时,将CPU密集型操作从一个线程转移到另一个线程,您希望实现什么? - Alexei Levenkov
我理解CPU密集型操作必须使用CPU。但我的问题是关于Web API和线程池的。如果我们可以以某种方式释放线程池,那么为什么不这样做呢?对于No.1重操作,来自线程池的线程将会阻塞,直到整个操作完成。 - Arian
2个回答

9

我不确定何时何地应该使用Task.Run。

在ASP.NET中,几乎从来不需要使用。你可以将“从不”作为一般规则。

何时何地我应该(可以)编写异步Web API?

只有当控制器操作调用异步代码时,您的控制器操作才应该是异步的。一般来说,对于执行I/O的所有代码,您应该优先考虑最低级别的异步API。然后,仅当操作方法需要调用该异步代码时,它们才是异步的。

因为没有异步版本,所以我应该同步编写Web方法...但是对于第二种方法,我可以这样更改

是的。

有没有办法异步运行第一个API?

可以这样想:如果需要同步(CPU密集型)工作,则该工作需要线程。它已经在线程池线程上运行(因为Web请求在来自线程池的线程上运行)。 Task.Run 将该工作移动到另一个线程池线程,但仍将在线程池线程上运行。因此, Task.Run 不会有任何帮助;它只会增加开销,提供不了任何好处。因此,“不要在ASP.NET上使用”是一般规则。


我相信你只需要为这个特定的问题提供最后一段,因为OP似乎对常规请求运行的位置感到困惑。(额外信息+9000) - Alexei Levenkov
@StephenCleary:谢谢亲爱的朋友。您能否看一下我的编辑1。我想澄清这些问题,并帮助像我这样有同样问题的人。 - Arian
1
@Arian:不是,也不是。真正的异步代码不使用线程 - Stephen Cleary
@StephenCleary:您能否向我展示一些链接和线索,指导如何将同步方法转换为异步版本?谢谢。 - Arian
我还有一个问题。是否有可能真正地将每个同步方法编写为异步方法? - Arian
1
@Arian:我建议从最低层开始使用异步API,然后让async在代码库中逐渐增长。不可能将每个同步方法都编写为真正的异步方法;许多方法只是自然同步的。 - Stephen Cleary

1
不要使用Task.Run将同步API变成异步。
在ASP.Net(FX和Core)中使用Task.Run是一种反模式。
当你看到/使用Task.Run时,期望的是你正在卸载将在后台完成的工作。在ASP.Net中,您可能认为这样做可以完成HttpRequest并将响应发送回调用者,而无需等待逻辑实际完成,这样做的想法是通过尽早响应来使前端响应灵敏。
对许多人来说,这听起来像是一种合法的异步处理解决方案,特别是在使用API时,尤其是在桌面应用程序和甚至MVC中,这非常有效,但在Web API中,许多线程在关联请求完成(或中止)时被中止。这意味着您的后台处理也可能会被中止。
在ASP.Net中,背景处理的最佳实践,特别是如果您需要支持大规模或高频率的请求,是使用具有不同服务运行时的队列模式来处理队列中的项目,与http请求过程本身完全解耦。
作为一个例子,Azure平台鼓励这种模式,允许您将“web jobs”部署到运行ASP.Net实现的同一个“App Service”中。这种托管托管模型期望您的HTTP处理尽可能短暂,所有CPU密集型、异步或并行处理都以消息或事件的形式提交到队列中。然后,当项目添加到队列中时,您可以编写触发执行的Web jobs(或函数)。

Stephen Cleary的这个答案是绝对正确的,这是一个补充帖子,探讨了人们甚至尝试在这种情况下使用Task.Run的常见原因。

以下帖子探讨了在ASP.Net中可以和不能使用后台线程的不同方式:

在ASP.Net中长时间运行的过程中,一个关键问题是Web主机进程可能会在很短的时间内终止你的运行逻辑,这在日志中很难跟踪,并且比你预期的要频繁发生。与其与这种自然现象作斗争,不如利用HTTP运行时安排工作由实际的后台进程完成。这样做的一个副作用是,它将使您能够更有效地扩展用户基础。

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