I thought I understood async
/await
and Task.Run()
quite well until I came upon this issue:
I'm programming a Xamarin.Android app using a RecyclerView
with a ViewAdapter
. In my OnBindViewHolder Method, I tried to async load some images
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:
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).
If I make the OnBindViewHolder
method async and use await instead of Task.Run, the code crashes on
imageView.SetImageBitmap(bitmap);
Saying that it's not in the UI thread, which makes totally sense to me.
So why does the async
/await
code crash while the Task.Run() doesn't?
Update: Answer
Since the Task.Run was not awaited, the thrown exception was not shown. If I awaitet the Task.Run, there was the error i expected. Further explanations are found in the answers below.
Async methods are intended to be non-blocking operations. An await expression in an async method doesn't block the current thread while the awaited task is running. Instead, the expression signs up the rest of the method as a continuation and returns control to the caller of the async method.
As you probably recall, await captures information about the current thread when used with Task. Run . It does that so execution can continue on the original thread when it is done processing on the other thread.
NET, Task. Run is used to asynchronously execute CPU-bound code. Let's say there is a method which does some CPU-bound work. Example : looping through a large array and doing some complex computation on each element of the array.
Inside an async function, you can use the await keyword before a call to a function that returns a promise. This makes the code wait at that point until the promise is settled, at which point the fulfilled value of the promise is treated as a return value, or the rejected value is thrown.
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.
Add "await" in front of the Task.Run, and you'll get the exception.
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");}); }
Task.Run()
and the UI thread should be used for a different purpose:
Task.Run()
should be used for CPU-bound methods.By moving your code into Task.Run()
, you avoid the UI thread from being blocked. 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.
What you should do instead is to call your UI related method on the UI thread. In Xamarin, you can run stuff on the UI thread by using 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) });
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.
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:
https://blog.stephencleary.com/2013/11/taskrun-etiquette-examples-dont-use.html https://blog.stephencleary.com/2013/11/taskrun-etiquette-examples-using.html
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With