Использование "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();
}