我正在试验提高 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 中,我替换了Thread.Sleep(2000)
with 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 方法响应时,它会释放线程并让其他请求执行。