【问题标题】:Multiple parallel awaitable tasks多个并行等待任务
【发布时间】:2015-11-07 16:36:23
【问题描述】:

我正在试验提高 ASP.NET 应用程序性能的方法。我正在研究的一件事是使用并行性并使操作异步以尝试减少处理时间并提高吞吐量。我首先模拟了一些我们经常做的事情,发出多个数据库检索来呈现一个页面。

public ActionResult Index()
{
    var dal = new Dal();
    var cases = new List<Case>();
    cases.AddRange( dal.GetAssignedCases() );
    cases.AddRange( dal.GetNewCases() );
    return View( "Cases", cases );
}

这两个 Dal 方法使用Thread.Sleep(2000) 来模拟查询并仅返回硬编码对象的集合。我使用 Apache Bench 使用 ab -c 1 -n 1 运行它,大约需要四秒钟。我第一次尝试改进它是:

public ActionResult Index()
{
    var dal = new Dal();
    var assignedCases = Task.Factory.StartNew( () => dal.GetAssignedCases() );
    var newCases = Task.Factory.StartNew( () => dal.GetNewCases() );
    IEnumerable<Case>[] allCases = Task.WhenAll( assignedCases, newCases ).Result;
    return View( "Cases", allCases.SelectMany( c => c ) );
}

当我使用相同的 ab 命令运行它时,它显示大约两秒钟,这是有道理的,因为我正在运行两个任务,每个任务都需要两秒钟,但它们是并行运行的。

当我将基准更改为 10 个并发请求(即ab -n 10 -c 10)时,我得到以下信息。

Fulfilled  Original Parallel
 50%         4014     2038
 66%         4015     2039
 75%         4017     4011

其余高达 100% 的数字在两列中相似。

我假设我在这里遇到的是线程池争用。大约 2/3 的请求会很快得到满足,然后等待线程为请求提供服务。所以我想也许如果我在混合中添加异步,我可以更快地获得更多请求。那就是我开始遇到问题的地方,我不知道问题是我模拟长时间运行的查询的方式还是我使用语言功能的方式,或者我是否完全走错了路隧道尽头是一列迎面而来的火车。 :-)

我做的第一件事是创建一个 DalAsync。在 DalAsync 中,我用await Task.Delay(2000) 替换了Thread.Sleep(2000),用async 关键字标记了每个方法,并将返回类型从IEnumerable&lt;Case&gt; 更改为Task&lt;IEnumerable&lt;Case&gt;&gt;。然后,我根据我在六篇博客文章和 MSDN 文章中阅读的信息编写了一个新的控制器方法。

public async Task<ActionResult> Index()
{
    var dal = new DalAsync();
    var assignedCases = dal.GetAssignedCasesAsync();
    var newCases = dal.GetNewCasesAsync();
    var allCases = await Task.WhenAll( assignedCases, newCases );
    return View( "Cases", allCases.SelectMany( c => c ) );
}

当我使用ab 运行它时,它永远不会完成,即使有一个请求它最终也会超时。我还尝试了以下变体,它有效,但返回的数字几乎与原始版本相同(这有点道理,因为我似乎再次序列化查询)。

var assignedCases = await dal.GetAssignedCasesAsync();
var newCases = await dal.GetNewCasesAsync();
var allCases = new List<Case>( assignedCases );
allCases.AddRange( newCases );

我希望发生的是:

  • 并行运行两个查询
  • 当控制器等待 Dal 方法响应时,它会释放线程并让其他 请求执行。

【问题讨论】:

标签: c# task-parallel-library async-await


【解决方案1】:

您的第一个代码示例应该可以工作,但是在我看来它有点奇怪。 Task.WhenAll 被引入为非阻塞操作,即您将使用 await Task.WhenAll(myTasks)。通过使用.Result,您正在将它变成一个阻塞操作,但是以这种方式使用感觉并不完全自然。

我认为你真正追求的是Task.WaitAll(params Task[]),它被设计成一个阻塞操作。

然而,您的第二个代码示例看起来近乎完美,正是我想要的。在整个代码库中实现异步代码总是会使实现更加简洁。

【讨论】:

    【解决方案2】:

    虽然我无法重现您的案例并且我的运行正常,但对我来说,您似乎陷入了僵局。尝试强制您的异步任务将其结果返回到不同的同步上下文,如下所示:

    public async Task<ActionResult> Index()
    {
        var dal = new DalAsync();
        var assignedCases = Task.Run(async () => await dal.GetAssignedCasesAsync());
        var newCases = Task.Run(async () => await dal.GetNewCasesAsync());
        var allCases = await Task.WhenAll( assignedCases, newCases).ConfigureAwait(false);
        return View( "Cases", allCases.SelectMany( c => c ) );
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-06-04
      • 1970-01-01
      • 2019-10-07
      • 2018-08-09
      • 1970-01-01
      • 1970-01-01
      • 2021-10-21
      相关资源
      最近更新 更多