Пример 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 ()?
может кто-нибудь объяснить мне, почему существует тупик в приведенном выше примере?
Я совершенно не знаю, в чем проблема ...
5 ответов:
посмотрите пример в здесь, Стивен имеет четкий ответ для вас:
Так вот что происходит, начиная с метода верхнего уровня (Button1_Click для UI / MyController.Получить для ASP.NET):
верхнего уровня вызовов методов GetJsonAsync (в рамках пользовательского интерфейса/АСП.Чистая контексте).
GetJsonAsync начинает запросу остальное по телефону с помощью HttpClient.GetStringAsync (все еще в пределах контекст.)
GetStringAsync возвращает незавершенную задачу, указывая, что запрос REST не завершен.
GetJsonAsync ждет задания, возвращаемый, возвращаемого getstringasync. Контекст захвачен и будет использоваться для продолжения выполнения GetJsonAsync метод позже. GetJsonAsync возвращает незавершенную задачу, указывает, что метод GetJsonAsync не завершен.
метод верхнего уровня синхронно блокирует о задаче, возвращенной GetJsonAsync. Это блокирует поток.
... В конце концов, запрос на отдых будет завершен. Это завершает задачу, возвращенную GetStringAsync.
продолжение для GetJsonAsync теперь готово к запуску, и он ждет, пока контекст будет доступен, чтобы он мог выполняться в контексте.
тупик. Метод блокирует поток, ждет GetJsonAsync для завершения, и GetJsonAsync ждет контекст должен быть свободным, чтобы он мог завершиться. Для примера пользовательского интерфейса, "контекст" - это контекст пользовательского интерфейса; для ASP.NET например, "контекст" - это the ASP.NET контекст запроса. Этот тип взаимоблокировки может быть вызван для либо "контекст".
еще одна ссылка, которую вы должны прочитать:
- Факт 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 асинхронным. Вы получите исключение, если вы делаете.
таким образом, в основном простой обходной путь в сценарии, где вы хотите вызвать асинхронный метод из метода синхронизации, вы можете сделать следующее:
- перед вызовом очистите SynchronizationContext
- сделать звонок, больше не будет тупик здесь, подождите, пока он закончит
- восстановить 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