我的目标是创建一个MVC控制器Action,它可以异步执行长时间运行的i / o操作。我的目标是避免在这个长时间运行的方法完成时绑定ASP.Net线程池中的线程。
The Action进行了两次调用。
第一个调用是第三方dll,它不包含任何异步方法。这个DLL从专有数据库读取并执行相当复杂的cpu绑定处理。返回可能需要几秒钟的时间。
第二个调用将第一个调用的结果用作使用Entity Framework传递给数据库查询的参数。
简化,这是行动:
public async Task<ActionResult> MyActionAsync(arg1, arg2)
{
var parameters = 3rdPartyComponent.TakesLongTime(arg1, arg2);
Task<List<MyClass>> genericList = null;
using (DbContexts.MyDbContext db = new DbContexts.MyDbContext())
{
genericList = await db.Database.SqlQuery<MyClass>(sql,parameters).ToListAsync();
}
return View("MyView", genericList);
}
我想打电话给3rdPartyComponent。我最初的想法是这样做:
var parameters = await Task.Run(() => 3rdPartyComponent.TakesLongTime()).ConfigurateAwait(false);
但我已经读过几位主题专家明确表示在asp.net MVC Action中使用Task.Run()会产生反作用,永远不应该这样做。
3rdPartyComponent是一个黑盒子的编译代码,不能改变它来添加异步方法。
有没有办法让对3rdPartyComponent的调用等待,以便整个Action在不占用asp.net线程池中的线程的情况下运行?
答案 0 :(得分:6)
第一个调用是第三方dll,它不包含任何异步方法。这个dll从专有数据库中读取并执行相当复杂的cpu绑定处理。
我想打电话给3rdPartyComponent。
有没有办法让对3rdPartyComponent的调用等待,以便整个Action在不占用asp.net线程池中的线程的情况下运行?
代码已经尽可能好了。 Task.Run
和Task.Factory.StartNew
都不会给您带来任何好处(即使您传递了LongRunning
标志)。
由于第三方dll执行CPU绑定代码,因此需要一个线程。即使您可以修改它,也只能使db访问(I / O工作)异步;根据定义,CPU工作是同步的,从你的描述来看,它听起来像CPU工作的工作是大部分时间。
关于在ASP.NET上避免Task.Run
(甚至更糟糕的Task.Factory.StartNew
)的全部观点是它们会导致行为效率降低。通过释放ASP.NET请求线程,您只需将线程返回到线程池,而不使用它;关于ASP.NET请求线程,没有什么神奇或特殊的东西。所以Task.Run
通过切换到另一个线程池线程来释放一个线程池线程,Task.Factory.StartNew
和LongRunning
通过创建一个全新的线程释放一个线程池线程,将工作安排到该线程,然后在最后拆除该线程(此行为未记录,也未指定,但它是当前观察到的行为)。
所以你最终做的只是招致不必要的线程切换(在StartNew
的情况下,是一个完整的额外线程)。 ASP.NET旨在处理同步和异步工作;如果你有同步代码,那么最好的办法就是直接执行它,就像你的代码已经完成一样。
答案 1 :(得分:1)
是的,我认为在异步服务器方法中使用Task.Run
是一种反模式,因为你安排的工作最终会在ThreadPool中被重新排队,而这正是你刚刚来到的地方from ... net gain zero(减去在ThreadPool中调度回调的开销)。
我的第一个想法是用
解雇工作Task.Factory.StartNew(action,TaskCreationOptions.LongRunning)
TaskCreationOptions.LongRunning
提示应该在新的Thread而不是ThreadPool中完成工作。
...但是如果这是一种繁重的流量方法,那么你冒着创建线程的速度比他们清理的速度快,现在你已经失去了ThreadPool的管理优势。
如果交通真的像你说的那么高,并且需要这种特殊处理,那么某些节流和/或缓存可能是有序的......但那将是一个不同的问题......