了解async / await和Task.Run()

时间:2018-04-24 14:36:41

标签: c# asynchronous xamarin async-await task

在我遇到这个问题之前,我认为我完全理解async / awaitTask.Run()

我正在使用带有RecyclerView的{​​{1}}对Xamarin.Android应用进行编程。在我的 OnBindViewHolder 方法中,我尝试异步加载一些图像

ViewAdapter

然后,在我的 LoadImage 函数中,我做了类似的事情:

public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
{
    // Some logic here

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

代码工作。但为什么?

我已经了解到,在将await设置为false后,代码不会在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; } (即UI线程)中运行。

如果我使SynchronizationContext方法异步并使用await而不是Task.Run,​​则代码崩溃

OnBindViewHolder

说它不在UI线程中,这对我来说非常有意义。

那么,为什么imageView.SetImageBitmap(bitmap); / async代码会在Task.Run()没有出现时崩溃?

更新:回答

由于未等待Task.Run,​​因此未显示抛出的异常。如果我等待Task.Run,​​那就是我预料到的错误。进一步的解释见下面的答案。

4 个答案:

答案 0 :(得分:10)

Task.Run()和UI线程应该用于不同的目的:

  • Task.Run()应该用于 CPU绑定方法
  • UI-Thread应该用于 UI相关方法

通过将代码移动到Task.Run(),可以避免阻止UI线程。这可能会解决您的问题,但这不是最佳做法,因为它对您的表现有害。 Task.Run()阻塞线程池中的线程。

您应该做的是在UI线程上调用与UI相关的方法。在Xamarin中,您可以使用 Device.BeginInvokeOnMainThread() 在UI线程上运行内容:

// 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)
});

即使你没有在UI线程上明确地调用它,它之所以有效,可能是因为Xamarin以某种方式检测到它应该在UI线程上运行并将这项工作转移到UI线程。

这是Stephen Cleary的一些有用的文章,它帮助我写了这个答案,它将帮助你进一步理解异步代码:

https://blog.stephencleary.com/2013/11/taskrun-etiquette-examples-dont-use.html https://blog.stephencleary.com/2013/11/taskrun-etiquette-examples-using.html

答案 1 :(得分:7)

它就像你没有等待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 :(得分:3)

可能UI访问仍然会引发UIKitThreadAccessException。您没有观察到它,因为您没有在await返回的标记上使用Task.Wait()关键字或Task.Run()。请参阅有关StackOverflow的Catch an exception thrown by an async method讨论,关于该主题的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;
});

通常,来自非UI线程的UI访问可能会使应用程序不稳定或崩溃,但它不能得到保证。

有关详细信息,请查看有关StackOverflow的How to handle Task.Run ExceptionAndroid - Issue with async tasks讨论,Stephen Toub的The meaning of TaskStatus文章和Microsoft Docs上的Working with the UI Thread文章。

答案 3 :(得分:-2)

Task.Run正在排队LoadImage以使用ConfigureAwait(false)在线程池上执行异步进程。 LoadImage正在返回的任务不在等待,我相信这是重要的部分。

因此Task.Run的结果是它立即返回Task<Task>,但外部任务没有设置ConfigureAwait(false),所以整个事情在主线程上解析。 / p>

如果您将代码更改为

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

我希望您能够点击线程未在UI线程上运行的错误。