Использование "async" (даже если оно должно завершиться) как часть маршрута MVC, блокирует маршрут; как этого можно избежать?
Рассмотрим следующее (на основе шаблона MVC по умолчанию), который представляет собой упрощенную версию некоторого "материала", который происходит в фоновом режиме - он отлично завершен и показывает ожидаемый результат, 20:
public ActionResult Index()
{
var task = SlowDouble(10);
string result;
if (task.Wait(2000))
{
result = task.Result.ToString();
}
else
{
result = "timeout";
}
ViewBag.Message = result;
return View();
}
internal static Task<long> SlowDouble(long val)
{
TaskCompletionSource<long> result = new TaskCompletionSource<long>();
ThreadPool.QueueUserWorkItem(delegate
{
Thread.Sleep(50);
result.SetResult(val * 2);
});
return result.Task;
}
Однако теперь, если мы добавим в микс async
:
public static async Task<long> IndirectSlowDouble(long val)
{
long result = await SlowDouble(val);
return result;
}
и измените первую строку маршрута на:
var task = IndirectSlowDouble(10);
тогда не работает; время от времени. Если мы добавим точки останова, return result;
в методе async
произойдет только после, маршрут уже завершен - в основном, похоже, что система не хочет использовать какой-либо поток для возобновления async
до тех пор, пока запрос не будет завершен. Хуже того: если бы мы использовали .Wait()
(или получили доступ к .Result
), тогда он будет полностью заторможен.
Итак: что с этим? Очевидным обходным решением является "не включать async
", но это нелегко при использовании библиотек и т.д. В конечном счете нет функциональной разницы между SlowDouble
и IndirectSlowDouble
(хотя очевидная структурная разница).
Примечание: точно такая же вещь в консоли /winform/etc будет работать нормально.
Ответы
Ответ 1
Это связано с тем, как контекст синхронизации реализован в ASP.NET(Pre.NET 4.5). Там тонны вопросов об этом поведении:
Task.WaitAll повешение с несколькими ожидаемыми задачами в ASP.NET
Asp.net SynchronizationContext блокирует HttpApplication для продолжений async?
В ASP.NET 4.5 появилась новая реализация контекста синхронизации, описанного в этой статье.
http://blogs.msdn.com/b/webdev/archive/2012/11/19/all-about-httpruntime-targetframework.aspx
Ответ 2
Когда вы используете .Result
, всегда существует возможность блокировки, потому что .Result
блокируется по своей природе. Способ избежать взаимоблокировок - не блокировать задачи (вы должны использовать async
и await
до конца). Предмет подробно описан здесь:
Одно исправление заключается в добавлении ConfigureAwait
:
public static async Task<long> IndirectSlowDouble(long val)
{
long result = await SlowDouble(val).ConfigureAwait(false);
return result;
}
Ответ 3
Другое решение - использовать async
/await
:
public async Task<ActionResult> Index()
{
var task = IndirectSlowDouble(10);
long result = await task;
ViewBag.Message = result.ToString();
return View();
}