多个并行的等待任务

时间:2013-07-18 17:00:42

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

我正在尝试提高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运行它,大约需要4秒钟。我尝试改进它的第一次尝试是:

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中,我将Thread.Sleep(2000)替换为await Task.Delay(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方法响应时,释放线程并释放其他线程 请求执行。

2 个答案:

答案 0 :(得分:0)

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

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

然而,你的第二个代码示例看起来接近完美,正是我想要的。在整个代码库中实现异步代码总能实现更清晰的实现。

答案 1 :(得分:0)

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

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