Понимание async/wait и Task.Run()
Я думал, что я понял, async
/await
и Task.Run()
довольно хорошо, пока я не пришел на этот вопрос:
Я программирую приложение Xamarin.Android, используя RecyclerView
с ViewAdapter
. В моем методе OnBindViewHolder я попытался выполнить асинхронную загрузку некоторых изображений
public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
{
// Some logic here
Task.Run(() => LoadImage(postInfo, holder, imageView).ConfigureAwait(false));
}
Затем в моей функции LoadImage я сделал что-то вроде:
private async Task LoadImage(PostInfo postInfo, RecyclerView.ViewHolder holder, ImageView imageView)
{
var image = await loadImageAsync((Guid)postInfo.User.AvatarID, EImageSize.Small).ConfigureAwait(false);
var byteArray = await image.ReadAsByteArrayAsync().ConfigureAwait(false);
if(byteArray.Length == 0)
{
return;
}
var bitmap = await GetBitmapAsync(byteArray).ConfigureAwait(false);
imageView.SetImageBitmap(bitmap);
postInfo.User.AvatarImage = bitmap;
}
Эти фрагменты кода работали. Но почему?
То, что я узнал, после ожидания configure установлено в значение false, код не запускается в SynchronizationContext
(который является потоком пользовательского интерфейса).
Если я сделаю метод OnBindViewHolder
асинхронным и OnBindViewHolder
использовать вместо Task.Run, код выйдет из строя
imageView.SetImageBitmap(bitmap);
Говорить, что это не в потоке пользовательского интерфейса, что имеет для меня совершенно смысл.
Так почему же async
/await
аварии коды в то время как Task.Run() не делает?
Обновление: ответ
Поскольку Task.Run не ожидалось, исключенное исключение не было показано. Если я ожидаю Task.Run, я ожидал ошибку. Дальнейшие объяснения можно найти в ответах ниже.
Ответы
Ответ 1
Это так же просто, как вы не ожидаете Task.Run, поэтому исключение попадает и не возвращается на сайт вызова Task.Run.
Добавьте "ждут" перед Task.Run, и вы получите исключение.
Это не приведет к сбою вашего приложения:
private void button1_Click(object sender, EventArgs e)
{
Task.Run(() => { throw new Exception("Hello");});
}
Это приведет к сбою вашего приложения:
private async void button1_Click(object sender, EventArgs e)
{
await Task.Run(() => { throw new Exception("Hello");});
}
Ответ 2
Task.Run()
и поток пользовательского интерфейса должны использоваться для другой цели:
-
Task.Run()
следует использовать для связанных с CPU методов. - UI-Thread должен использоваться для связанных с UI методов.
Перемещая свой код в Task.Run()
, вы избегаете блокировки потока пользовательского интерфейса. Это может решить вашу проблему, но это не самая лучшая практика, потому что это плохо для вашей работы. Task.Run()
блокирует поток в пуле потоков.
Вместо этого вам следует называть ваш связанный с UI метод в потоке пользовательского интерфейса. В Xamarin вы можете запускать материал в потоке пользовательского интерфейса с помощью Device.BeginInvokeOnMainThread()
:
// async is only needed if you need to run asynchronous code on the UI thread
Device.BeginInvokeOnMainThread(async () =>
{
await LoadImage(postInfo, holder, imageView).ConfigureAwait(false)
});
Причина, по которой он работает, даже если вы явно не называете его в потоке пользовательского интерфейса, вероятно, потому, что Xamarin каким-то образом обнаруживает, что это то, что должно запускаться в потоке пользовательского интерфейса, и переносит эту работу на поток пользовательского интерфейса.
Вот несколько полезных статей Стивена Клири, которые помогли мне написать этот ответ и помогут вам понять асинхронный код:
https://blog.stephencleary.com/2013/11/taskrun-etiquette-examples-dont-use.html https://blog.stephencleary.com/2013/11/taskrun-etiquette-examples-using.html
Ответ 3
Вероятно, доступ к UI по-прежнему вызывает UIKitThreadAccessException
. Вы не наблюдаете это, потому что не используете ключевое слово await
или Task.Wait()
для маркера, Task.Run()
возвращает Task.Run()
. См. Catch исключение, вызванное обсуждением метода async в StackOverflow, документация MSDN по этой теме немного устарела.
Вы можете присоединить продолжение к маркеру, который Task.Run()
возвращает и проверяет исключения, брошенные внутри пройденного действия:
Task marker = Task.Run(() => ...);
marker.ContinueWith(m =>
{
if (!m.IsFaulted)
return;
// Marker Faulted status indicates unhandled exceptions. Observe them.
AggregateException e = m.Exception;
});
В общем, доступ к пользовательскому интерфейсу из неинтерфейса может сделать приложение неустойчивым или сбой, но это не гарантируется.
Для получения дополнительной информации проверьте, как обрабатывать Task.Run Exception, Android - Проблема с обсуждениями асинхронных задач в StackOverflow, Значение статьи TaskStatus Стивена Тууба и Работа с статьей потока пользовательского интерфейса в Microsoft Docs.
Ответ 4
Task.Run
LoadImage
для выполнения асинхронного процесса в пуле потоков с помощью ConfigureAwait(false)
. Задача, LoadImage
возвращает LoadImage
, НЕ ожидается, хотя я считаю, что это важная часть здесь.
Таким образом, результаты Task.Run
том, что он немедленно возвращает Task<Task>
, но внешняя задача не имеет ConfigureAwait(false)
, поэтому вся вещь решается на основном потоке.
Если вы измените свой код на
Task.Run(async () => await LoadImage(postInfo, holder, imageView).ConfigureAwait(false));
Я ожидаю, что вы ударите ошибку, когда поток не работает в потоке пользовательского интерфейса.