在我遇到这个问题之前,我认为我完全理解async
/ await
和Task.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,那就是我预料到的错误。进一步的解释见下面的答案。
答案 0 :(得分:10)
Task.Run()
和UI线程应该用于不同的目的:
Task.Run()
应该用于 CPU绑定方法。通过将代码移动到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 Exception,Android - 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线程上运行的错误。