【问题标题】:C# async/await for I/O-Bound vs CPU-Bound operationC# async/await for I/O-Bound vs CPU-Bound 操作
【发布时间】:2018-07-15 10:17:33
【问题描述】:

我正在学习 C# 中的异步编程。在this 文章中,我发现对于 IO-Bound 操作,您不应该使用 Task.Run() 但我不知道如何在没有 Task.Run() 的情况下创建任务......例如我想创建一个自定义名为 GetPageCountAsync 的方法,它查询数据库并返回结果。我已经有方法 GetPageCount 可以同步查询数据库。我不知道比以下更好的方法:

private async Task<int> GetPageCountAsync()
{
  return await Task.Run(() => GetPageCount());
}

如何在没有 Task.Run 的情况下执行此操作?我找到了 SqlCommand 类的 ExecuteReaderAsync 方法,但我想知道这个方法是如何实现的?没有Task.Run?如果有,怎么做?

【问题讨论】:

标签: c# asynchronous io async-await cpu


【解决方案1】:

要让你的方法异步,你所要做的就是等待其中的异步操作,并像这样制作方法签名(假设这里的返回值......):

async Task<int> GetPageCountAsync()
{
    //not shown is how to set up your connection and command
    //nor disposing resources
    //nor validating the scalar value
    var pages = await command.ExecuteScalarAsync().ConfigureAwait(false);
    return (int)pages;
}

如果您在使用的库中没有异步方法可以调用,您可以create your own awaitables,但极少需要用到这样的长度。

现在GetPageCountAsync 是异步的,你只需等待它:

return await GetPageCountAsync();

对于像这样的非上下文感知代码,允许代码在另一个上下文中恢复也是一个很好的做法,例如:

return await GetPageCountAsync().ConfigureAwait(false);

如果你还有其他工作要做,不依赖于查询结果,你可以启动它,稍后等待它并行执行工作:

 Task pageCountTask = GetPageCountAsync();
//do something not dependent on page count....
return await pageCountTask;

【讨论】:

  • 我认为 OP 想知道如何创建异步 IO 而不是如何使用它 -> 这并不能真正回答 IMO 的问题
  • @ChrFin 确实,我想如何在不使用 Task.Run() 方法的情况下创建它们
  • 感谢您编辑您的答案。我快速检查了内部有关创建诸如将 Action 或 Func 转换为 Task 之类的等待对象的链接并等待它们,它看起来正是我正在寻找的东西。
  • 嗨@Klemenko,我也有同样的疑问,我们想如何创建自己的 I/O 绑定异步等待方法。请您分享您对如何实施它的想法。我已经引用了链接,但无法完全理解它
【解决方案2】:

有两个术语很容易混淆:多任务和多线程。

多线程是多任务的一种形式。很长一段时间以来,它实际上是我们可以使用的唯一多任务处理方式。虽然我们可以在没有线程的情况下进行多任务处理,但编写起来通常很麻烦。在 GUI 环境中编写和使用 BackgroundWorker 更容易,然后编写适当的多任务处理。

但是线程有问题:需要调用。他们喜欢吞下例外。别忘了,我们实际上需要一个事件队列来进行回调。

我认为 C# 5.0 添加了 Async 和 await。目标似乎是允许多任务处理没有多线程或过度需要编写代码。编译器和运行时将为您在同一线程内的上下文之间进行所有烦人的切换。您仍然可以为此使用线程。您必须将它们用于 CPU 绑定操作。但在许多情况下,使用多任务处理更简单、更好。

【讨论】:

  • 我从未说过它们不存在。我说:“虽然我们可以在没有线程的情况下进行多任务处理,但编写起来通常很麻烦。在 GUI 环境中编写和使用 BackgroundWorker 更容易,然后编写适当的多任务处理。”我指出,虽然它存在,但使用多线程进行多任务处理通常只容易几个数量级。
  • 哦,所以您从未说过,“这是我们拥有的多任务处理的唯一方式”此外,您将 BGW 称为执行无线程并行的较旧方式,但 BGW 执行它的使用线程的并行性,因此这甚至不是如何在没有多线程的情况下并行工作的正确示例。
  • 我将其更改为“长期以来,它实际上是我们可以使用的唯一多任务处理方式。”现在开心吗?
  • 不,因为它仍然是错误的,也因为你还没有真正回答这个问题。对 C# 中的异步历史做出一堆事实上不正确的陈述并不能回答在执行此特定异步操作时如何避免多线程的问题。
  • @Servy:所以你通常很生气,我无法让你开心。很高兴知道。意味着我可以不再关心你说的话:)
【解决方案3】:

您用于执行 IO 操作的任何框架都有责任为您提供固有的异步操作。它可以提供一个返回Task 的方法,也可以提供一些其他异步操作(即接受回调、返回IAsyncResult、触发事件等的方法),这些操作可以转换为Task

如果您正在调用的操作已经同步阻塞线程,直到异步结果完成,那么已经为时已晚。 大多数 DB 框架擅长提供某种形式的异步查询数据库的方式,因此很可能还有另一种方法可供您使用。如果您使用的框架提供任何异步版本的方法,那么您无法避免使用多线程;框架的作者已经从你手中夺走了这个选择。

【讨论】:

  • 很好的答案,但现在假设我是框架的所有者,我想提供一些异步功能。怎么样?
  • @Klemenko 这将完全取决于您正在执行的操作。在 IO 的情况下,操作固有是异步的,因此它们已经异步,除非您特意阻止当前线程直到它们完成。
  • 我想我理解你的意思,但可以肯定的是:如果我必须调用一些 WCF 同步方法怎么办:作为客户端,除了阻止线程等待一个要求完成?那是相当低效的。
  • @FrenchBoiethios 在这种情况下,底层操作本质上是异步的,但是您正在使用的为您执行操作的库会同步阻塞,直到异步操作完成,并且可能不会以任何方式公开异步完成相关工作。那不是因为操作不能异步完成,只是选择已经脱离了你的掌控。
  • @Servy 我明白了。感谢您抽出宝贵时间回复。
猜你喜欢
  • 1970-01-01
  • 2022-01-10
  • 2010-11-20
  • 2011-04-14
  • 1970-01-01
  • 1970-01-01
  • 2019-09-24
  • 1970-01-01
  • 2018-10-09
相关资源
最近更新 更多