我有一个ASP.NET MVC异步操作方法,如下所示:
public async Task<ActionResult> IndexAsync()
{
var tasks = new List<Task<string>>
{
GetSomethingAsync("a"),
GetSomethingAsync("b")
};
await Task.WhenAll(tasks);
return View();
}
private async Task<string> GetSomethingAsync()
{
var data = await _someService.GetSomethingAsync().ConfigureAwait(false);
return data.SomeData;
}
现在,当我调试并跳过tasks
变量创建时,任务立即执行。换句话说,当我将鼠标悬停在tasks
行的await
上时,他们会说“RanToCompletion”。
为什么?
根据我的理解,应该创建任务,但是在await Task.WhenAll(tasks)
阻止调用触发之前处于“WaitingForActivation”状态。
有人可以向我解释发生了什么事吗?我之前编写过这样的代码,它通常按预期工作,所以我想知道这是ASP.NET还是ASP.NET MVC异步控制器?
TIA。
修改的 如果我将代码更改为:
var tasks = new List<Task<string>>
{
Task.Run(() => GetSomethingAsync("a")),
Task.Run(() => GetSomethingAsync("b"))
};
该方法按预期运行(任务在await
之前未执行)。
在运行异步任务之前,我一般都不需要这样做,ASP.NET MVC是否需要这样做?
答案 0 :(得分:3)
根据你的评论,你实际上没有任何真正的异步代码 - 所以确实任务将同步返回并处于完成状态。
使方法成为真正异步的最简单方法是await Task.Yield()
。这对于单元测试或由于某种原因必须异步但不会消耗太多时间的方法很好。如果您需要运行缓慢(阻塞或仅CPU密集型)方法 - Task.Run
,就像您在问题中一样,这是使任务在单独的线程上运行的合理方法。
注释
async
本身不会使其异步,也不会await
自己创建任何线程。await
方法将无法找到要运行的线程。ConfigureAwait(false)
并不能阻止ASP.Net中基于加载的死锁,并且方便地丢失HttpContext.Current
和线程的CultureInfo
- 请特别注意在ASP.Net中使用它基本上没有这方面的优势(与WPF / WinForm跨线程调用相比,在新线程上恢复上下文的成本非常低)。答案 1 :(得分:2)
现在,当我调试并跳过任务变量创建时,任务立即执行。换句话说,当我将鼠标悬停在await行中的任务上时,他们会说“RanToCompletion”。
我建议你阅读我的async
intro。引用:
异步方法的开头就像任何其他方法一样执行。也就是说,它会同步运行,直到遇到“await”(或抛出异常)。
因此,如果您已经删除了返回已完成任务的存根异步方法,那么您的方法调用(例如,GetSomethingAsync("a")
)将同步完成。该任务已添加到列表中时已完成。
根据我的理解,应该创建任务,但是处于“WaitingForActivation”状态,直到由await Task.WhenAll(tasks)阻止调用触发。
WaitingForActivation
是一个不幸的名字。 For Promise Tasks, the WaitingForActivation
state means it's actually already in progress.
Task.WhenAll
或await
不需要“触发”。 Task.Run(() => GetSomethingAsync("a"))
创建的任务已经。
通过在await Task.Delay(1000);
之前插入await Task.WhenAll
并在延迟之后检查任务的状态,可以在调试器中观察到这一点。
是否需要ASP.NET MVC?
没有。事实上,you should avoid Task.Run
on ASP.NET。引用我的关于异步ASP.NET的MSDN文章:
你可以通过等待Task.Run开始一些后台工作,但这样做没有意义。事实上,这实际上会通过干扰ASP.NET线程池启发式来损害您的可伸缩性。如果你有关于ASP.NET的CPU限制工作,最好的办法是直接在请求线程上执行它。作为一般规则,不要将工作排队到ASP.NET上的线程池。