C# 在ASP.NET中正确使用Task.Run和Task.WaitAll进行EF调用
简短版本: 任何人都可以确认,对于ASP.NET应用程序,带有EF调用的非异步子函数上的Task.Run/Task.WaitAll是否有任何好处 长版本: 我们有一个Asp.NETWebAPI服务,它有一个方法,可以使用EntityFramework进行多个DB调用,以收集一组数据,这些数据都返回到单个响应对象中。我们将其分解为一系列子函数,并希望这些子函数并行运行注意:它们都不使用任何异步调用 为了实现某种形式的并行编码,我们使用Task.Run调用每个函数,然后是Task.WaitAll:C# 在ASP.NET中正确使用Task.Run和Task.WaitAll进行EF调用,c#,asp.net,async-await,task,C#,Asp.net,Async Await,Task,简短版本: 任何人都可以确认,对于ASP.NET应用程序,带有EF调用的非异步子函数上的Task.Run/Task.WaitAll是否有任何好处 长版本: 我们有一个Asp.NETWebAPI服务,它有一个方法,可以使用EntityFramework进行多个DB调用,以收集一组数据,这些数据都返回到单个响应对象中。我们将其分解为一系列子函数,并希望这些子函数并行运行注意:它们都不使用任何异步调用 为了实现某种形式的并行编码,我们使用Task.Run调用每个函数,然后是Task.WaitAll:
public ResponseObject PopulateResponseObject(int id)
{
var response = new ResponseObject();
Task<DataSetA> dataSetATask = Task.Run(() => getDataSetA(id));
Task<DataSetB> dataSetBTask = Task.Run(() => getDataSetB(id));
Task.WaitAll(dataSetATask, dataSetBTask);
response.SetA = dataSetATask.Result;
response.SetB = dataSetBTask.Result;
return response;
}
从我所读到的内容来看,Task.Run在Asp.Net应用程序中可能不会获得太多收益,而我们在这里所做的可能只会导致不必要的线程池开销
我们这样写代码是在浪费时间吗
更新:
我们熟悉EF有异步版本,但有大量代码需要修改。我们希望这些函数保持原样。使用Task.Run至少可以让您并行运行两个查询,因此与按顺序运行查询的完全同步版本相比,可以获得改进 但是,收益仅限于threadpool中的线程数,而线程数最终会受到服务器容量的限制。通过使用EF异步API,您将获得更多收益,因为这将允许执行许多查询,而无需为每个查询绑定线程
但这是开始使用异步代码的合理策略。完成此操作后,您可以返回并缓慢地将查询转换为使用异步api。使用Task.Run至少允许您并行运行两个查询,因此与按顺序运行查询的完全同步版本相比,可以获得改进 但是,收益仅限于threadpool中的线程数,而线程数最终会受到服务器容量的限制。通过使用EF异步API,您将获得更多收益,因为这将允许执行许多查询,而无需为每个查询绑定线程
但这是开始使用异步代码的合理策略。完成此操作后,您可以返回并慢慢地将查询转换为使用异步api。根据我的经验,我强烈建议您不要为了微不足道的性能提高而扼杀简单性。在我看来,这种并行性的影响是微不足道的,它带来了巨大的开发、维护和调试负担
特别是因为您使用EF并与DB交互,所以您可能会面临许多可能是mare的并发性问题!另外,我建议您看看。根据我的经验,我强烈建议您不要为了微不足道的性能提升而扼杀简单性。在我看来,这种并行性的影响是微不足道的,它带来了巨大的开发、维护和调试负担
特别是因为您使用EF并与DB交互,所以您可能会面临许多可能是mare的并发性问题!另外,我建议看一下。在使用上述方法对Sync、Async和Parallel.Invoke进行了一些性能测试之后,我发现了一些有趣的结果。并行版本类似于:
public ResponseObject PopulateResponseObject(int id)
{
var response = new ResponseObject();
Parallel.Invoke(
() => response.SetA = getDataSetA(id),
() => response.SetB = getDataSetB(id),
);
return response;
}
异步出现在上面,在我最初的帖子中
结果表明,与基本同步调用相比,Async调用Task.Run/Task.WaitAll平均在57%的时间内运行,Parallel.Invoke平均在65%的时间内运行
因此,在ASP.NET应用程序中使用Task.Run/WaitAll有一个优势
更新:顺便说一句,这个测试是通过将我们的代码推送到我们的测试环境来完成的,我们的QA人员在那里执行测试。它当然没有模仿我们的prod环境,但确实显示它稍微好一些。在对Sync、Async(使用上述方法)和Parallel.Invoke进行了一些性能测试之后,我发现了一些有趣的结果。并行版本类似于:
public ResponseObject PopulateResponseObject(int id)
{
var response = new ResponseObject();
Parallel.Invoke(
() => response.SetA = getDataSetA(id),
() => response.SetB = getDataSetB(id),
);
return response;
}
异步出现在上面,在我最初的帖子中
结果表明,与基本同步调用相比,Async调用Task.Run/Task.WaitAll平均在57%的时间内运行,Parallel.Invoke平均在65%的时间内运行
因此,在ASP.NET应用程序中使用Task.Run/WaitAll有一个优势
更新:顺便说一句,这个测试是通过将我们的代码推送到我们的测试环境来完成的,我们的QA人员在那里执行测试。它当然没有模仿我们的prod环境,但确实表明它稍微好一些。您可能只需要使用一个Parallel.ForEach重载来节省一些麻烦。这取决于使用模式。如果你的端点很少被调用,你可能会提高性能,特别是在一些长时间运行的东西的情况下,但通常不值得这么麻烦。这将使用比需要多得多的线程。创建async await是为了保存线程使用情况。EF有异步API。@Marie-好建议。我刚刚实现了一个Parallel.Invoke版本和
我正在测试。当系统处于负载状态时,不可能判断并行运行这些程序是否会产生任何好处。如果您正在处理来自多个用户的请求,那么线程可能很容易成为稀缺资源,您可能最终会等待一个线程可用。由于并发锁,您可能还会遇到线程之间的阻塞。判断是否存在总体效益的唯一方法是模拟预期负载,并查看系统如何响应。另外,请参见。您可能只需要使用Parallel.ForEach重载之一来保存一些hassleIt,这取决于使用模式。如果你的端点很少被调用,你可能会提高性能,特别是在一些长时间运行的东西的情况下,但通常不值得这么麻烦。这将使用比需要多得多的线程。创建async await是为了保存线程使用情况。EF有异步API。@Marie-好建议。我刚刚实现了一个Parallel.Invoke版本,正在测试它。当系统处于负载状态时,无法判断并行运行这些版本是否会产生任何好处。如果您正在处理来自多个用户的请求,那么线程可能很容易成为稀缺资源,您可能最终会等待一个线程可用。由于并发锁,您可能还会遇到线程之间的阻塞。判断是否存在总体效益的唯一方法是模拟预期负载,并查看系统如何响应。另外,请参见。