Пример async / await, который вызывает взаимоблокировку



я наткнулся на некоторые рекомендации по асинхронному программированию с использованием ключевых слов async/await c#(я новичок в c# 5.0).



один из приведенных советов был следующим:



стабильность: знайте свои контексты синхронизации



...
Некоторые контексты синхронизации не являются реентерабельными и однопоточными. Это означает, что только одна единица работы может быть выполнена в контексте в данный момент времени. Примером этого является поток пользовательского интерфейса Windows или ASP.NET контекст запроса.
В этих однопоточных контекстах синхронизации легко заблокировать себя. Если вы создаете задачу из однопоточного контекста, а затем ждете эту задачу в контексте, ваш код ожидания может блокировать фоновую задачу.



public ActionResult ActionAsync()
{
// DEADLOCK: this blocks on the async task
var data = GetDataAsync().Result;

return View(data);
}

private async Task<string> GetDataAsync()
{
// a very simple async method
var result = await MyWebService.GetDataAsync();
return result.ToString();
}


если я попытаюсь рассечь его сам, основной поток порождает новый в "MyWebService.GetDataAsync ();", но так как основной поток ждет там, он ждет результата в "GetDataAsync ().Результат." Между тем, говорят данные готовый. Почему основной поток не продолжает логику продолжения и возвращает результат строки из GetDataAsync ()?



может кто-нибудь объяснить мне, почему существует тупик в приведенном выше примере?
Я совершенно не знаю, в чем проблема ...

638   5  

5 ответов:

посмотрите пример в здесь, Стивен имеет четкий ответ для вас:

Так вот что происходит, начиная с метода верхнего уровня (Button1_Click для UI / MyController.Получить для ASP.NET):

  1. верхнего уровня вызовов методов GetJsonAsync (в рамках пользовательского интерфейса/АСП.Чистая контексте).

  2. GetJsonAsync начинает запросу остальное по телефону с помощью HttpClient.GetStringAsync (все еще в пределах контекст.)

  3. GetStringAsync возвращает незавершенную задачу, указывая, что запрос REST не завершен.

  4. GetJsonAsync ждет задания, возвращаемый, возвращаемого getstringasync. Контекст захвачен и будет использоваться для продолжения выполнения GetJsonAsync метод позже. GetJsonAsync возвращает незавершенную задачу, указывает, что метод GetJsonAsync не завершен.

  5. метод верхнего уровня синхронно блокирует о задаче, возвращенной GetJsonAsync. Это блокирует поток.

  6. ... В конце концов, запрос на отдых будет завершен. Это завершает задачу, возвращенную GetStringAsync.

  7. продолжение для GetJsonAsync теперь готово к запуску, и он ждет, пока контекст будет доступен, чтобы он мог выполняться в контексте.

  8. тупик. Метод блокирует поток, ждет GetJsonAsync для завершения, и GetJsonAsync ждет контекст должен быть свободным, чтобы он мог завершиться. Для примера пользовательского интерфейса, "контекст" - это контекст пользовательского интерфейса; для ASP.NET например, "контекст" - это the ASP.NET контекст запроса. Этот тип взаимоблокировки может быть вызван для либо "контекст".

еще одна ссылка, которую вы должны прочитать:

жду, и UI, и тупики! О боже!

  • Факт 1: GetDataAsync().Result; будет работать, когда задача возвращенным GetDataAsync() завершается, в то же время он блокирует поток пользовательского интерфейса
  • Факт 2: Продолжение ждут (return result.ToString()) помещается в очередь к потоку пользовательского интерфейса для выполнения
  • Факт 3: Задача возвращена GetDataAsync() завершится при запуске его продолжения в очереди
  • Факт 4: продолжение в очереди никогда не выполняется, потому что поток пользовательского интерфейса заблокирован (факт 1)

тупик!

тупик может быть нарушен предоставленными альтернативами, чтобы избежать факта 1 или факта 2.

  • не 1,4. Вместо блокировки потока пользовательского интерфейса используйте var data = await GetDataAsync(), что позволяет потоку пользовательского интерфейса продолжать работать
  • избежать 2,3. Очередь продолжение ожидания в другой поток, который не заблокирован, например use var data = Task.Run(GetDataAsync).Result, который отправит продолжение в контекст синхронизации потока threadpool. Это позволяет выполнить задачу возвращается GetDataAsync() завершить.

это очень хорошо объясняется в статья исполнителя Stephen Toub, примерно на полпути вниз, где он использует пример DelayAsync().

еще один главный момент заключается в том, что вы не должны блокировать задачи и использовать асинхронность полностью, чтобы предотвратить взаимоблокировки. Тогда это будет все асинхронная не синхронная блокировка.

public async Task<ActionResult> ActionAsync()
{

    var data = await GetDataAsync();

    return View(data);
}

private async Task<string> GetDataAsync()
{
    // a very simple async method
    var result = await MyWebService.GetDataAsync();
    return result.ToString();
}

Я просто возился с этой проблемой снова в a MVC.Net проект. Если вы хотите вызвать асинхронные методы из PartialView, вы не можете сделать PartialView асинхронным. Вы получите исключение, если вы делаете.

таким образом, в основном простой обходной путь в сценарии, где вы хотите вызвать асинхронный метод из метода синхронизации, вы можете сделать следующее:

  1. перед вызовом очистите SynchronizationContext
  2. сделать звонок, больше не будет тупик здесь, подождите, пока он закончит
  3. восстановить SynchronizationContext

пример:

    public ActionResult DisplayUserInfo(string userName)
    {
        // trick to prevent deadlocks of calling async method 
        // and waiting for on a sync UI thread.
        var syncContext = SynchronizationContext.Current;
        SynchronizationContext.SetSynchronizationContext(null);

        //  this is the async call, wait for the result (!)
        var model = _asyncService.GetUserInfo(Username).Result;

        // restore the context
        SynchronizationContext.SetSynchronizationContext(syncContext);

        return PartialView("_UserInfo", model);
    }

работа вокруг я пришел, чтобы использовать Join расширением метода на задачу, прежде чем спрашивать за результат.

код выглядит так:

public ActionResult ActionAsync()
{
  var task = GetDataAsync();
  task.Join();
  var data = task.Result;

  return View(data);
}

где метод соединения:

public static class TaskExtensions
{
    public static void Join(this Task task)
    {
        var currentDispatcher = Dispatcher.CurrentDispatcher;
        while (!task.IsCompleted)
        {
            // Make the dispatcher allow this thread to work on other things
            currentDispatcher.Invoke(delegate { }, DispatcherPriority.SystemIdle);
        }
    }
}

Я не достаточно в домен, чтобы увидеть недостатки этого решения (если есть)

Comments

    Ничего не найдено.