繁体   English   中英

多个并行的等待任务

[英]Multiple parallel awaitable tasks

我正在尝试改善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)来模拟查询,并仅返回硬编码对象的集合。 我使用ab -c 1 -n 1和Apache Bench一起运行,大约需要四秒钟。 我第一次尝试改善它是:

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<Case>更改为Task<IEnumerable<Case>> 然后,我根据六篇博客文章和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方法响应时,它将释放线程并让其他请求执行。

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

我认为您真正Task.WaitAll(params Task[])Task.WaitAll(params Task[]) ,它被设计为阻塞操作。

但是,您的第二个代码示例看起来非常完美,正是我想要的。 在整个代码库中实现异步代码始终可以使实现更加简洁。

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

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 ) );
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM