Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding async / await and Task.Run()

Tags:

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.

like image 541
Tobias von Falkenhayn Avatar asked Apr 24 '18 14:04

Tobias von Falkenhayn


People also ask

What is the difference between Task run and async await?

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.

What does await Task Run do?

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.

Does Task Run Run async?

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.

How does async and await work?

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.


2 Answers

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");}); } 
like image 165
Robin Avatar answered Sep 20 '22 02:09

Robin


Task.Run() and the UI thread should be used for a different purpose:

  • Task.Run() should be used for CPU-bound methods.
  • UI-Thread should be used for UI related 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

like image 31
Dennis Schröer Avatar answered Sep 22 '22 02:09

Dennis Schröer