Понимание async / await и Task.Run () - PullRequest
0 голосов
/ 24 апреля 2018

Я думал, что достаточно хорошо понял 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;
}

Эти кусочки кода сработали .Но почему?

Что я узнал, после того как для настройки конфигурации установлено значение false, код не запускается в SynchronizationContext (который является потоком пользовательского интерфейса).

Если я сделаю OnBindViewHolder метод async и использование await вместо Task.Run, код падает на

imageView.SetImageBitmap(bitmap);

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

Так почему же происходит сбой кода async / await, а Task.Run () не происходит?

Обновление: ответ

Поскольку Task.Run не ожидался, выброшенное исключение не было показано.Если я жду Task.Run, произошла ошибка, которую я ожидал.Дальнейшие объяснения можно найти в ответах ниже.

Ответы [ 4 ]

0 голосов
/ 03 мая 2018

Вероятно, доступ к пользовательскому интерфейсу все еще выдает UIKitThreadAccessException.Вы не наблюдаете это, потому что вы не используете ключевое слово await или Task.Wait() для маркера, который возвращает Task.Run().См. Поймать исключение, выданное асинхронным методом обсуждение 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 , Android - проблема с асинхронными задачами обсуждение StackOverflow, Значение TaskStatus статья Стивена Тауба и Работа с потоком пользовательского интерфейса статья в Microsoft Docs.

0 голосов
/ 26 апреля 2018

Task.Run() и поток пользовательского интерфейса следует использовать для других целей:

  • Task.Run() следует использовать для методов, связанных с ЦП .
  • Поток пользовательского интерфейса следует использовать для методов, связанных с пользовательским интерфейсом .

Перемещая код в Task.Run(), вы избегаете блокировки потока пользовательского интерфейса.Это может решить вашу проблему, но это не лучшая практика, потому что это плохо сказывается на вашей работе.Task.Run() блокирует поток в пуле потоков.

Вместо этого вам следует вызвать метод, связанный с пользовательским интерфейсом, в потоке пользовательского интерфейса.В 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

0 голосов
/ 03 мая 2018

Это так же просто, как и то, что вы не ожидаете Task.Run, поэтому исключение съедается и не возвращается на сайт вызова Task.Run.

Добавьте «await» перед 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");});
}
0 голосов
/ 24 апреля 2018

Task.Run ставит в очередь LoadImage для выполнения асинхронного процесса в пуле потоков с ConfigureAwait(false).Задача, которую возвращает LoadImage, НЕ ожидается, и я считаю, что это важная часть здесь.

Таким образом, результатом Task.Run является то, что он немедленно возвращает Task<Task>, но внешняя задача не имеет установленной ConfigureAwait(false), поэтому вместо этого все решается в основном потоке.

Если вы измените свой код на

Task.Run(async () => await LoadImage(postInfo, holder, imageView).ConfigureAwait(false)); 

Я ожидаю, что вы получите сообщение об ошибке, что поток не выполняется в потоке пользовательского интерфейса.

...