Предисловие : я ищу объяснение, а не просто решение. Я уже знаю решение.
Несмотря на то, что я потратил несколько дней на изучение статей MSDN об асинхронном шаблоне на основе задач (TAP), асинхронности и ожидания, я все еще немного озадачен некоторыми мелочами.
Я пишу регистратор для приложений Магазина Windows и хочу поддерживать как асинхронную, так и синхронную регистрацию. Асинхронные методы следуют за TAP, синхронные должны скрывать все это и выглядеть и работать как обычные методы.
Это основной метод асинхронного ведения журнала:
private async Task WriteToLogAsync(string text)
{
StorageFolder folder = ApplicationData.Current.LocalFolder;
StorageFile file = await folder.CreateFileAsync("log.log",
CreationCollisionOption.OpenIfExists);
await FileIO.AppendTextAsync(file, text,
Windows.Storage.Streams.UnicodeEncoding.Utf8);
}
Теперь соответствующий синхронный метод ...
Версия 1 :
private void WriteToLog(string text)
{
Task task = WriteToLogAsync(text);
task.Wait();
}
Это выглядит правильно, но это не работает. Вся программа зависает навсегда.
Версия 2 :
Хм .. Может задача не была запущена?
private void WriteToLog(string text)
{
Task task = WriteToLogAsync(text);
task.Start();
task.Wait();
}
Это кидает InvalidOperationException: Start may not be called on a promise-style task.
Версия 3:
Хм .. Task.RunSynchronously
звучит многообещающе.
private void WriteToLog(string text)
{
Task task = WriteToLogAsync(text);
task.RunSynchronously();
}
Это кидает InvalidOperationException: RunSynchronously may not be called on a task not bound to a delegate, such as the task returned from an asynchronous method.
Версия 4 (решение):
private void WriteToLog(string text)
{
var task = Task.Run(async () => { await WriteToLogAsync(text); });
task.Wait();
}
Это работает. Итак, 2 и 3 - неправильные инструменты. А 1? Что не так с 1 и в чем разница с 4? Что заставляет 1 вызвать замораживание? Есть ли проблема с объектом задачи? Есть ли неочевидный тупик?
источник
Ответы:
await
Внутри асинхронного метода пытается вернуться в поток пользовательского интерфейса.Поскольку поток пользовательского интерфейса занят ожиданием завершения всей задачи, у вас возникла тупиковая ситуация.
Перемещение асинхронного вызова для решения
Task.Run()
проблемы.Поскольку асинхронный вызов теперь выполняется в потоке пула потоков, он не пытается вернуться к потоку пользовательского интерфейса, и поэтому все работает.
В качестве альтернативы, вы можете вызвать
StartAsTask().ConfigureAwait(false)
перед ожиданием внутренней операции, чтобы она вернулась в пул потоков, а не в поток пользовательского интерфейса, полностью избегая взаимоблокировки.источник
ConfigureAwait(false)
подходящее решение в этом случае. Поскольку ему не нужно вызывать обратные вызовы в захваченном контексте, это не нужно. Будучи методом API, он должен обрабатывать его внутренне, а не заставлять всех вызывающих программ выходить из контекста пользовательского интерфейса.Microsoft.Bcl.Async
.Вызов
async
кода из синхронного кода может быть довольно сложным.Полные причины этого тупика я объясняю в своем блоге . Короче говоря, есть «контекст», который сохраняется по умолчанию в начале каждого
await
и используется для возобновления метода.Поэтому, если это вызывается в контексте пользовательского интерфейса, когда
await
завершается,async
метод пытается повторно войти в этот контекст, чтобы продолжить выполнение. К сожалению, код, использующийWait
(илиResult
), заблокирует поток в этом контексте, поэтомуasync
метод не может быть завершен.Руководящие принципы, чтобы избежать этого:
ConfigureAwait(continueOnCapturedContext: false)
как можно больше. Это позволяет вашимasync
методам продолжать выполнение без повторного входа в контекст.async
все пути. Используйтеawait
вместоResult
илиWait
.Если ваш метод естественно асинхронный, то вы (вероятно) не должны предоставлять синхронную оболочку .
источник
async
как бы я это сделал и предотвратить возникновение пожара и забыть.await
поддерживается вcatch
блоках с VS2015. Если вы используете более старую версию, вы можете назначить исключение локальной переменной и выполнитьawait
после блока catch .Вот что я сделал
отлично работает и не блокирует поток пользовательского интерфейса
источник
При небольшом настраиваемом контексте синхронизации функция синхронизации может ожидать завершения асинхронной функции, не создавая взаимоблокировку. Вот небольшой пример для приложения WinForms.
источник