Я столкнулся с некоторыми передовыми практиками асинхронного программирования с использованием ключевых слов 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().Result
. Между тем говорят, что данные готовы. Почему основной поток не продолжает свою логику продолжения и не возвращает строковый результат GetDataAsync()
?
Может кто-нибудь объяснить мне, почему в приведенном выше примере тупик? Я совершенно не понимаю, в чем проблема ...
источник
var data = GetDataAsync().Result;
это строка кода, которая никогда не должна выполняться в контексте, который вы не должны блокировать (запрос пользовательского интерфейса или ASP.NET). Даже если он не блокируется, он блокирует поток на неопределенное время. В общем, это ужасный пример. [Вам нужно выйти из потока пользовательского интерфейса перед выполнением подобного кода или использоватьawait
его также, как предлагает Тони.]Ответы:
Взгляните на этот пример , у Стивена есть для вас четкий ответ:
Еще одна ссылка, которую вы должны прочитать: Await, UI и взаимоблокировки! Боже мой!
источник
GetDataAsync().Result;
запускается, когда задача, возвращенная функцией,GetDataAsync()
завершается, в то время как он блокирует поток пользовательского интерфейсаreturn result.ToString()
) помещено в очередь для выполнения в поток пользовательского интерфейса.GetDataAsync()
будет завершена, когда будет выполнено ее продолжение в очереди.Тупик!
Из тупика можно выйти с помощью предоставленных альтернатив, чтобы избежать Факт 1 или Факт 2.
var data = await GetDataAsync()
, что позволяет потоку пользовательского интерфейса продолжать работуvar data = Task.Run(GetDataAsync).Result
незаблокированном потоке , например use , который отправит продолжение в контекст синхронизации потока пула потоков. Это позволяет выполнить задачу, возвращенную пользователемGetDataAsync()
.Это очень хорошо объясняется в статье Стивена Туба , примерно на полпути, где он использует пример
DelayAsync()
.источник
var data = Task.Run(GetDataAsync).Result
это ново для меня. Я всегда думал, что внешний.Result
будет легко доступен, как только будет запущен первый await ofGetDataAsync
, и такdata
будет всегдаdefault
. Интересно.Я просто снова возился с этой проблемой в проекте ASP.NET MVC. Если вы хотите вызвать
async
методы из aPartialView
, вам не разрешено создаватьPartialView
async
. Если вы это сделаете, вы получите исключение.Вы можете использовать следующий простой обходной путь в сценарии, когда вы хотите вызвать
async
метод из метода синхронизации: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); }
источник
Еще один важный момент заключается в том, что вы не должны блокировать Задачи и полностью использовать асинхронный режим, чтобы предотвратить взаимоблокировки. Тогда это будет асинхронная, а не синхронная блокировка.
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(); }
источник
Я пришел к решению использовать
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); } } }
Я недостаточно разбираюсь в предметной области, чтобы увидеть недостатки этого решения (если таковые имеются)
источник