简体   繁体   English

了解async / await和Task.Run()

[英]Understanding async / await and Task.Run()

I thought I understood async / await and Task.Run() quite well until I came upon this issue: 我以为我理解了async / awaitTask.Run()直到我遇到这个问题:

I'm programming a Xamarin.Android app using a RecyclerView with a ViewAdapter . 我正在使用带有ViewAdapterRecyclerView编写Xamarin.Android应用程序。 In my OnBindViewHolder Method, I tried to async load some images 在我的OnBindViewHolder方法中,我尝试异步加载一些图像

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

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

Then, in my LoadImage function I did something like: 然后,在我的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;
}

That pieces of code worked . 这段代码有效 But why? 但为什么?

What I've learned, after configure await is set to false, the code doesn't run in the SynchronizationContext (which is the UI thread). 我所学到的,在configure await设置为false之后,代码不会在SynchronizationContext (这是UI线程)中运行。

If I make the OnBindViewHolder method async and use await instead of Task.Run, the code crashes on 如果我使OnBindViewHolder方法异步并使用await而不是Task.Run,​​代码崩溃

imageView.SetImageBitmap(bitmap);

Saying that it's not in the UI thread, which makes totally sense to me. 说它不在UI线程中,这对我来说非常有意义。

So why does the async / await code crash while the Task.Run() doesn't? 那么为什么async / await代码崩溃而Task.Run()没有呢?

Update: Answer 更新:答案

Since the Task.Run was not awaited, the thrown exception was not shown. 由于没有等待Task.Run,​​因此未显示抛出的异常。 If I awaitet the Task.Run, there was the error i expected. 如果我等待Task.Run,​​那就是我预料到的错误。 Further explanations are found in the answers below. 进一步的解释见下面的答案。

Task.Run() and the UI thread should be used for a different purpose: Task.Run()和UI线程应该用于不同的目的:

  • Task.Run() should be used for CPU-bound methods . Task.Run()应该用于CPU绑定方法
  • UI-Thread should be used for UI related methods . UI-Thread应该用于UI相关的方法

By moving your code into Task.Run() , you avoid the UI thread from being blocked. 通过将代码移动到Task.Run() ,可以避免阻止UI线程。 This may solve your issue, but it's not best practice because it's bad for your performance. 这可能会解决您的问题,但这不是最佳做法,因为它对您的表现有害。 Task.Run() blocks a thread in the thread pool. Task.Run()阻塞线程池中的线程。

What you should do instead is to call your UI related method on the UI thread. 你应该做的是在UI线程上调用你的UI相关方法。 In Xamarin, you can run stuff on the UI thread by using Device.BeginInvokeOnMainThread() : 在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)
});

The reason why it's working even if you don't explicitly call it on the UI thread is probably because Xamarin somehow detects that it's something that should run on the UI thread and shifts this work to the UI thread. 即使你没有在UI线程上显式调用它,它的工作原因可能是因为Xamarin以某种方式检测到它应该在UI线程上运行并将此工作转移到UI线程。

Here are some useful articles by Stephen Cleary which helped me to write this answer and which will help you to further understand asynchronous code: 以下是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 https://blog.stephencleary.com/2013/11/taskrun-etiquette-examples-dont-use.html https://blog.stephencleary.com/2013/11/taskrun-etiquette-examples-using.html

It's as simple as you not awaiting the Task.Run, so the exception gets eaten and not returned to the call site of Task.Run. 它就像你没有等待Task.Run一样简单,所以异常会被吃掉而不会返回到Task.Run的调用站点。

Add "await" in front of the Task.Run, and you'll get the exception. 在Task.Run前面添加“await”,你就会得到异常。

This will not crash your application: 这不会导致您的应用程序崩溃:

private void button1_Click(object sender, EventArgs e)
{
    Task.Run(() => { throw new Exception("Hello");});
}

This however will crash your application: 但是这会使您的应用程序崩溃:

private async void button1_Click(object sender, EventArgs e)
{
   await Task.Run(() => { throw new Exception("Hello");});
}

Probably UI access still throws UIKitThreadAccessException . 可能UI访问仍然会抛出UIKitThreadAccessException You do not observe it because you do not use await keyword or Task.Wait() on a marker that Task.Run() returns. 您没有观察它,因为您没有在Task.Run()返回的标记上使用await关键字或Task.Wait() See Catch an exception thrown by an async method discussion on StackOverflow, MSDN documentation on the topic is a bit dated. 请参阅关于StackOverflow上的异步方法讨论引发的异常 ,关于该主题的MSDN文档有点过时。

You can attach continuation to the marker that Task.Run() returns and inspect exceptions thrown inside an action passed: 您可以将continuation附加到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;
});

In general, UI access from non UI thread may make an application unstable or crash it, but it isn't guaranteed. 通常,来自非UI线程的UI访问可能会使应用程序不稳定或崩溃,但无法保证。

For more information check How to handle Task.Run Exception , Android - Issue with async tasks discussions on StackOverflow, The meaning of TaskStatus article by Stephen Toub and Working with the UI Thread article on Microsoft Docs. 有关更多信息,请参阅如何处理Task.Run ExceptionAndroid -在StackOverflow上进行异步任务讨论的问题,Stephen Toub 的TaskStatus文章的含义以及使用 Microsoft Docs上的UI Thread文章。

Task.Run is queuing LoadImage to execute the async process on the thread pool with ConfigureAwait(false) . Task.Run被排队LoadImage执行与线程池的异步处理ConfigureAwait(false) The task that LoadImage is returning is NOT awaited though and I believe that is the important part here. 不会等待LoadImage返回的任务,我相信这是重要的部分。

So the results of Task.Run is that it immediately returns a Task<Task> , but the outer task does not have ConfigureAwait(false) set, so the whole thing resolves on the main thread instead. 所以Task.Run的结果是它立即返回一个Task<Task> ,但是外部任务没有设置ConfigureAwait(false) ,所以整个事情在主线程上解析。

If you change your code to 如果您将代码更改为

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

I'm expecting you to hit the error that the thread isn't running on the UI thread. 我期待你点击线程没有在UI线程上运行的错误。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM