【问题标题】:c# webapi: Await Task.Run vs more granualar awaitc# web api: Await Task.Run vs 更细粒度的 await
【发布时间】:2016-02-09 22:55:57
【问题描述】:

根据本文,我在 WebApi 控制器中使用 async/await: https://msdn.microsoft.com/en-us/magazine/dn802603.aspx

看看我的控制器中的这个简化代码:

DataBaseData = await Task.Run( () => GetDataFunction()  );

GetDataFunction 是一个函数,它将打开一个数据库连接,打开一个阅读器并从数据库中读取数据。

在许多示例中,我看到它的处理方式不同。等待 GetDataFunction 本身。在函数中,每一步都在等待。 例如:

  • connection.OpenAsync
  • reader.ReadAsycnc
  • reader.IsDBNullAsync

为什么这是一个好的做法?为什么不为整个数据库访问启动一个线程(使用 Task.Run)?

更新: 谢谢您的帮助。现在我知道了。我没有得到,异步 Api 本身不会启动线程。这真的很有帮助:blog.stephencleary.com/2013/11/there-is-no-thread.html

【问题讨论】:

  • 我不明白 async/await 是如何工作的。但我不明白为什么我需要在 WebApi 控制器中进行精细的异步/等待处理。它用于确保将请求线程返回到池中。只需等待 Task.Run 也会这样做。
  • 有人能解释一下为什么我对此投反对票吗?

标签: c# asp.net-web-api async-await


【解决方案1】:

我假设你想知道为什么要使用

await Task.Run(() => GetDataFunction());

而不是

await GetDataFunction();

正如您在Task.Run(Func) 中看到的备注部分下写着:

语言编译器使用 Run(Func) 方法来支持 async 和 await 关键字。它不打算直接从用户代码中调用。

也就是说,你应该使用await GetDataFunction

【讨论】:

  • 好的,你是对的。不应使用 Task.Run。然而,如果我只是以通常的方式产生一个新线程呢?我的意思是,主要问题是,是否有必要使用非常精细的处理并等待 sql 读取操作的每一步,或者将整个数据库操作内容在单独的线程中执行也可以。这都是关于 Webapi 控制器中的请求池,而不是与 UI 相关的异步/等待。
  • 如果您不更新任务中的 GUI 相关项,您可以将任务跨越所有数据库操作。但是建议在 db-connection 上使用non-async 方法。
【解决方案2】:

这都是关于资源管理和共享的。

当使用connection.Open() 方法时,调用它的线程必须等待连接真正打开。在等待期间,它只会消耗操作系统分配给它的 CPU 片。但是,当您await connection.OpenAsync() 时,线程会释放其资源,操作系统可以将它们重新分配给其他线程。

现在,有一个问题:要真正从异步调用中获益,它们所花费的时间应该比操作系统切换上下文所需的时间更长,否则会导致应用程序性能下降。

connection.OpenAsync()reader.ReadAsync() 等上等待的做法是因为这些是可能长时间运行的操作。在数据库驻留在另一台机器上并且请求负载很重的系统中,连接到数据库并获取查询结果将需要一些时间。与其在等待结果到达时阻塞 CPU,为什么不将该时间片分配给在调度程序队列中等待为另一个客户端呈现响应的另一个工作线程?

现在,关于启动另一个数据访问线程:不要那样做!。每个新线程都被分配了大约 1MB 的内存空间,因此除了在等待数据库操作完成时浪费 CPU 时间之外,您还将浪费内存。此外,拥有如此大的内存占用将需要更多运行垃圾收集器,这会在运行时冻结所有线程

使用Task.Run() 将在来自ThreadPool 的线程上安排数据访问操作,但您将失去在等待数据库服务器响应时与另一个线程共享 CPU 时间的好处。

【讨论】:

    【解决方案3】:

    您链接到的文章指出:

    您可以通过等待 Task.Run 来启动一些后台工作,但这样做没有任何意义。事实上,这实际上会通过干扰 ASP.NET 线程池试探法而损害您的可伸缩性...作为一般规则,不要将工作排队到 ASP.NET 上的线程池中。

    换句话说,避免在 ASP.NET 上使用Task.Run

    为什么这是一个好的做法?为什么不为整个数据库访问启动一个线程(使用 Task.Run)?

    这是因为异步 API不使用其他线程async/await的全部意义在于释放当前线程; 使用另一个线程。我有一篇描述 how async works without needing threads 的博文。

    因此,Task.Run(或自定义线程)方法将在从数据库获取数据时用尽(阻塞)线程。正确的异步方法(例如,EF6 或 ADO.NET 异步 API)不;它们允许请求线程在该请求等待数据库响应时用于其他请求。

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-07-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-08
    • 2018-10-09
    • 2016-08-07
    • 2020-08-01
    相关资源
    最近更新 更多