【发布时间】:2014-05-28 12:49:53
【问题描述】:
我注意到在异步 ASP.NET Web API 控制器方法中 await 之后发生了意外的(我会说是冗余的)线程切换。
例如,下面我希望在 #2 和 3# 位置看到相同的 ManagedThreadId,但大多数情况下我会在 #3 看到不同的线程:
public class TestController : ApiController
{
public async Task<string> GetData()
{
Debug.WriteLine(new
{
where = "1) before await",
thread = Thread.CurrentThread.ManagedThreadId,
context = SynchronizationContext.Current
});
await Task.Delay(100).ContinueWith(t =>
{
Debug.WriteLine(new
{
where = "2) inside ContinueWith",
thread = Thread.CurrentThread.ManagedThreadId,
context = SynchronizationContext.Current
});
}, TaskContinuationOptions.ExecuteSynchronously); //.ConfigureAwait(false);
Debug.WriteLine(new
{
where = "3) after await",
thread = Thread.CurrentThread.ManagedThreadId,
context = SynchronizationContext.Current
});
return "OK";
}
}
我查看了AspNetSynchronizationContext.Post 的实现,基本上可以归结为:
Task newTask = _lastScheduledTask.ContinueWith(_ => SafeWrapCallback(action));
_lastScheduledTask = newTask;
因此,继续在 ThreadPool 上安排,而不是内联。这里,ContinueWith 使用 TaskScheduler.Current,根据我的经验,它始终是 ASP 中 ThreadPoolTaskScheduler 的一个实例.NET(但不一定是这样,见下文)。
我可以使用ConfigureAwait(false) 或自定义等待程序来消除像这样的冗余线程切换,但这会消除像HttpContext.Current 这样的HTTP 请求状态属性的自动流。
AspNetSynchronizationContext.Post 的当前实现还有另一个副作用。 以下情况会导致死锁:
await Task.Factory.StartNew(
async () =>
{
return await Task.Factory.StartNew(
() => Type.Missing,
CancellationToken.None,
TaskCreationOptions.None,
scheduler: TaskScheduler.FromCurrentSynchronizationContext());
},
CancellationToken.None,
TaskCreationOptions.None,
scheduler: TaskScheduler.FromCurrentSynchronizationContext()).Unwrap();
这个例子虽然有点做作,但展示了如果TaskScheduler.Current 是TaskScheduler.FromCurrentSynchronizationContext()(即由AspNetSynchronizationContext 制成)可能发生的情况。它不使用任何阻塞代码,并且可以在 WinForms 或 WPF 中顺利执行。
AspNetSynchronizationContext 的这种行为与 v4.0 实现不同(它仍然作为LegacyAspNetSynchronizationContext 存在)。
那么,这种变化的原因是什么?我想,这背后的想法可能是为了减少死锁的差距,但是在使用@987654341时,当前的实现仍然可能出现死锁@ 或 Task.Result。
IMO,这样说更合适:
Task newTask = _lastScheduledTask.ContinueWith(_ => SafeWrapCallback(action),
TaskContinuationOptions.ExecuteSynchronously);
_lastScheduledTask = newTask;
或者,至少,我希望它使用TaskScheduler.Default 而不是TaskScheduler.Current。
如果我在web.config 中启用LegacyAspNetSynchronizationContext 和<add key="aspnet:UseTaskFriendlySynchronizationContext" value="false" />,它会按预期工作:同步上下文安装在等待任务结束的线程上,并且继续在那里同步执行。
【问题讨论】:
标签: c# asp.net .net task-parallel-library async-await