Async / Await and Task best practices for Xamarin

About the topic:

Contenido

Async programming can be the magic below your mobile app development that lights your way. You can use async methods for long-running tasks like calling APIs, downloading data, or animating your UI dynamically.

Using async methods in a bad way can cause your app’s UI freeze and users can not use the app until the task completes. Yep, this is really bad for your User Experience (UX) and you may be aware of it.

 You can find a lot of great information out there, but here we are focusing our energy on best practices.

⚠ Note: I advise you to look for more information if you are not very familiar with asynchronous programming.

Once you have learned the basics of async and await and got fairly comfortable with it, you’re ready to go. I think that we all know that sometimes we do some bad practices because we are unaware of how some things work and of course there is a lot of scattered documentation that can be a little confusing at the beginning.

The idea of this article is to help you figure out best practices in order to improve your apps and skills, get a good dev/loop going, and in general have a great experience with Xamarin.

📘 Note: Here I’m going to show you just best practices, I’m not going deep in details talking about TPL (Task Parallel Library), State machine, and so on. But you will have all the references available just in case you want to go deep into something.

Async/Await

Maybe Async/Await is one of those topics that you feel like you know a lot about but then you don’t. The reality is there are a lot of different ways to use Async/Await, and that’s part of the problem. It’s hard to understand what each different method does, each different field on the tasks, what all the different keywords do, and how they differ because sometimes they’re very similar and they were only created out of necessity and efficiency.

 Async/Await is basically a syntaxis sugar on steroids to make calling async methods easier and to make your async code more easily readable.

Lets start with some best practice examples.

Invoking tasks

Let’s invoking tasks. Imagine that we have a typical task, a TaskService, and all this service it’s doing is running a delay internally. This might be very familiar if you’re using HttpClient, for example.

The TaskService is just invoking a task, once we have that task, what the services return is just a task string.

...
private void MyMethod()
{
     ...
     var taskResult = service.TaskService().Result;
     ...
}
...

This is a bad way to start with. We’re invoking the method, but then we’re calling the .Result at the end of it.

Here .Result will wait for the result of the task and when that happens is when we get the result, if the task hasn’t completed, it just waits in line. If the result comes back, then it can continue execution.

This is synchronous. You never want to do that in the UIThread.

How this can affect my app? When we are in the UIThread, we will block/freeze/lock the UI until we get the result.

🚨 Note: Don’t use .Result in your constructor or in animations, for example.

All right. Let’s look at how to fix this.

...
private async void MyMethod()
{
     ...
     var taskResult = await service.TaskService();
     ...
}
...

All we need to do is call await on the task and make our method async in order to get the result. But at the end behavior, we can still use the app, we can even run the method multiple times if you want, then it will complete the execution.

Awaiting multiple tasks

Once you’ve figured out that you can await tasks, the next thing we see is people await tasks separately. Imagine that you have like three REST services endpoints that you need to call, and you want to call them asynchronously.

...
private async void MyMethod()
{
     ...
     var taskResult1 = await service.TaskService1();
     ...
     var taskResult2 = await service.TaskService2();
     ...
     var taskResult3 = await service.TaskService3();
     ...
}
...

What this code does is: await the first one, await the second one, await the third one, and you know what happens? You’re awaiting, and awaiting, and awaiting. And you’re not going to get to the end until everything is done.

Suppose each service task is completed in one second, it will take 3 seconds to complete the task. Maybe you think this doesn’t look too bad because you’re awaiting, so you’re not locking your UI but you’ll see the first one go, the second one, the third one, and so on.

⚠ Note: When you have multiple long-running tasks you can feel the bad user experience.

Let’s take a look at the “Fix”.

...
private async void MyMethod()
{
     ...
     var tasks = new List<Task>();
     
     tasks.Add(service.TaskService1());
     tasks.Add(service.TaskService2()):
     tasks.Add(service.TaskService3());
     ...
     await Task.WhenAll(tasks);
     ...
}
...

What we are doing here is keeping the tasks in a collection. In this case, I’m putting them in a List, and then we can call “Task.WhenAll” on the whole collection.

WhenAll means that the task will only return once all the tasks are done. When a task is done, that means it either completed, or it was canceled, or its faulted through an exception.

⚠ Note: The task can be completed separately. If some of them come back early, that’s fine, it will just complete once the last task comes in.

This’s beautiful, right? Before the method will take 3 seconds to complete the task. But if we run them with Task.WhenAll, we’ll see that they all fired off, and then they all completed together in one second.

Threading

Before we run a task, when we hit a button and run a command, it actually executes on the main thread. Once we await the task, is it still the main thread, is it a different thread? Developers don’t think about this. It’s like: “Well, it came from the main thread, maybe it’s still there” or sometimes they don’t even think about the context because it awaits, it works, it’s just beautiful, it runs asynchronously.

But guess what, you have to be mindful of where it runs.

In the example below, imagine that we have a thread sleep to simulate consecutive long-running task operations. Let’s take a look at that.

...
private async Task MyMethod()
{
     ...
     var taskResult = await service.LongTaskService();

     //Simulate CPU intensive operation here
     ...
}
...

Here when we call out to the service, it’s going to start on the UI thread and actually await, do stuff in the background, come back onto the main thread, where threads are sleeping. Here you may think it’s on a background thread but it’s not because the await was on a background thread and continue the execution on the UI thread.

In this case, your app is going to be locked by your next intensive operation. Very similar to a .Result but we didn’t use it we are using await. This is tricky.

Let’s see how to fix this.

...
private async Task MyMethod()
{
     ...
     var taskResult = await service.LongTaskService().ConfigureAwait(false);

     //Simulate CPU intensive operation here
     ...
}
...

Here we just need to use the magic keyword “ConfigureAwait(false)“. By default, a task will have ConfigureAwait(true) which causes execution to continue on the UI thread.

When we say ConfigureAwait(false), we are saying I allow execution to continue on a background thread but this guarantee is that whatever happens afterward, isn’t going to be on the main thread.

Keep in mind that you rarely need to go back to the context where you were before. When Task.ConfigureAwait(false) is used, the code no longer tries to resume where it was before, instead, if possible, the code completes in the thread that completed the task, thus avoiding a context switch. This slightly boosts performance and can help avoid deadlocks as well.

This is particularly important when the method is called a large number of times, to have better responsiveness.

Tasks

Returning tasks

In the example below, in the AwaitStringTaskAsync method, we just return await instead of returning the task directly. This can be a helper method that might be doing some stuff, but we’ve added an async and await keyword here because we thought we had to.

...
private async void MyMethod()
{
     ...
     await AwaitStringTaskAsync();
     ...
}

private async Task<string> AwaitStringTaskAsync()
{
     return await service.GetStringAsync();
}
...

The reality is that we can actually do is just return the task, and that’s actually faster because we don’t have to await twice. Now, there’s await in-between which does context switches and all this stuff under the hood. So, what we actually are doing is adding an overhead.

Lets take a look how to fix this.

...
private async void MyMethod()
{
     ...
     await AwaitStringTaskAsync();
     ...
}

private Task<string> AwaitStringTaskAsync()
{
     return service.GetStringAsync();
}
...

Sometimes methods do not need to be async, but return a Task and let the other side handle it as appropriate. Note that if we don’t have return await, but return a Task instead, the return happens right away.

If the last sentence of your code is a return await you may actually consider refactoring it so that the return type of the method is Task<TResult> instead of async Task. With this, you are avoiding overhead, thus making your code leaner.

💡Tip: The only time we truly want to await is when we want to do something with the result of the async task in the method continuation.

Avoid “async void” methods

Void-returning async methods have a specific purpose: to make asynchronous event handlers possible.

When an exception is thrown out of an async Task or async Task<T> method, that exception is captured and placed on the Task object.

With async void methods, there is no Task object, so any exceptions thrown out of an async void method will be raised directly on the SynchronizationContext that was active when the async void method started.

...
public async void AsyncVoidMethod()
{
    //Bad!
}

public async Task AsyncTaskMethod()
{
    //Good!
}
...

💡 Tip: consider using async Task instead of async void.

In the example below the catch block inside of ThisWillNotCatchTheException() method will never be reached. But we can fix this just replacing the async void with async Task as you can see in the ThisWillCatchTheException() method.

...
public async void AsyncVoidMethodThrowsException()
{
    throw new Exception("Hmmm, something went wrong! :(");
}

public void ThisWillNotCatchTheException()
{
    try
    {
        AsyncVoidMethodThrowsException();
    }
    catch(Exception ex)
    {
        //The below line will never be reached
        Debug.WriteLine(ex.Message);
    }
}
...
public async Task AsyncTaskMethodThrowsException()
{
    throw new Exception("Hmmm, something went wrong!");
}

public async Task ThisWillCatchTheException()
{
    try
    {
        await AsyncTaskMethodThrowsException();
    }
    catch (Exception ex)
    {
        //The below line will actually be reached
        Debug.WriteLine(ex.Message);
    }
}
...

⚠ Note: Async void methods are hard to test and write UnitTest because of the error handling. So, if you do this consider working with async methods that return a Task.

Return Task inside try/catch or using block

Return Task can cause unexpected behavior used inside a try/catch block (an exception thrown by the async method will never be caught) or inside a using block because the task will be returned right away.

...
public Task<string> ReturnTaskExceptionNotCaught()
{
    try
    {
        return service.TaskService(); // Bad!
    }
    catch (Exception ex)
    {
        //The below line will never be reached
        Debug.WriteLine(ex.Message);
    }
}
...

In the first example above, if an exception is thrown inside a Task within TaskService(), it will not be caught by  ReturnTaskExceptionNotCaught() method, even if it’s inside the try/catch block, but will be caught in an outer method generated by the compiler that awaits on the task returned by ReturnTaskExceptionNotCaught().

There is no way of how to splain this without going into deep details. So, I leave you this thread if you want to go into more detail.

...
public Task<string> ReturnTaskIssueWithUsing()
{
    using (var resource = new Resource())
    {
        //By the time the resource is actually referenced, may have been disposed already
        return resource.TaskResource(); //Bad!
    }
}
...

In the other hand, the ReturnTaskIssueWithUsing method will Dispose() the Resource object as soon as the TaskResource() method returns, which is likely long before it actually completes. This means the method is probably buggy (because Resource is disposed too soon).

Lets see how to fix these scenarios!

At this point, you might know that try/catch change how exceptions are handled. Making our method async we can await the result and catch the exception that will be thrown.

💡 Tip: If you need to wrap your async code in a try/catch or using block, use return await instead.

💡 Tip: Remember, the only reason you’d want to add async/await to your method is that you want to manipulate the result before returning it.

...
public async Task<string> ReturnTaskExceptionNotCaught()
{
    try
    {
        return await service.TaskService(); // Good!
    }
    catch (Exception ex)
    {
        //The below line will be reached
        Debug.WriteLine(ex.Message);
    }
}

public async Task<string> ReturnTaskIssueWithUsing()
{
    using (var resource = new Resource())
    {
        return await resource.TaskResource(); //Good!
    }
}
...

Same as before, making our method async we can await the result and tell the method that can’t dispose Resource object because we are waiting for the result in order to continue with the process.

Handling exceptions

With tasks, we talked about .Result, we talked about await, but sometimes you want to “FireAndForget“. Sometimes a task can just run. We don’t care if it completes if it doesn’t complete. It’s just, runs.

In the example below, let’s say that we have an exception in the FireAndForgetTask but we don’t know that. This is the typical code that we just need to execute but we add a try/catch just in case something gets wrong.

...
public ICommand MyCommand => new Command(() =>
{
     try
     {
     ...
     //Something that can throw an exception
     service.FireAndForgetTask();
     ...
     }
     catch (Exception ex)
     {
         Console.WriteLine($"Exception occurred: {ex.Message}");
     }
});
...

In this case, what happens is because we’ve FireAndForgetTask, its execution will continue till we’re outside of the “catch block”. If any exception happens there, who knows where it goes. So, you’re going to depend on how your app is structured.

So, your command will end and then the exception occurred. Even thought your app is not crashed, your functionality may not work. This is almost worse than an app crashing.

Lets fix that!

...
public ICommand MyCommand => new Command(() =>
{
     ...
     //Something that can throw an exception
     service.FireAndForgetTask()
     .ContinueWith(continuationAction: (task) =>
     {
         Console.WriteLine($"Exception occurred: {task.Exception.Message}");
     }, continuationOptions: TaskContinuationOptions.OnlyOnFaulted);
     ...
});
...

All we do here is use “task.ContinueWith“. What ContinueWith does is, once our task completes, regardless, if we awaited it or not, this continuation will happen and we can pass in an Action. In this action, we have the Task as a parameter, so we can inspect our task.

We can also set these ContinuationOptions, where you can specify OnlyOnFaulted, OnlyOnCanceled, and much more in order to handle those scenarios.

💡 Tip: With ContinueWith you can’t have multiple “ContinueWith” on a single task. If you want to handle multiple different options, then just handle them all in the same “ContinueWith”.

Getting back to the example, using ContinueWith we do good exception handling, we’ll see the command ended, then the test faulted, and we have our exception print.

This is really nice if you’re logging that out into App Center or something else, now you actually get that exception.

Timing out tasks

Sometimes we want to set a time limit to some tasks, how we do that? Some people use Task.WaitAll because has a second parameter which is a timeout but the issue with this method is that run synchronously the same as before as .Result. Let’s take a look.

...
private void MyMethod()
{
     ...
     // 3 minutes task
     var longTaskService= service.LongTaskService();

     var tasks = new Task[] { longTaskService };
     var timeoutSeconds = 10;

     Task.WaitAll(tasks, timeout: TimeSpan.FromSeconds(timeoutSeconds));
     ...
}
...

In the example above we have a Task that will run for 3 minutes and we just want to wait 10 seconds per request. So, Task.WaitAll is doing the job for us because it has a second parameter which is a timeout. Take into a count that you are literally waiting. So, you must be careful with this cause you can lock your app.

Lets take a look an alternative.

/* View Model Context */
...
private async Task MyMethod()
{
     ...
     var timeoutSeconds = 10;
     var cancellationTokenSource = new CancellationTokenSource(delay: TimeSpan.FromSeconds(timeoutSeconds));
     
     // 3 minutes task
     await service.LongTaskService(cancellationTokenSource.Token);
     ...
}
...
...
/* Service Context */
...
public Task<string> LongTaskService(CancellationToken cancellationToken = default)
{
     var getStringTask = Task.Run(() =>
     {
        //Simulate work
        Task.Delay((int)TimeSpan.FromMinutes(3).TotalMilliseconds); 

        return "I took 3 min of your life :(";
     }, cancellationToken: cancellationToken);

     return getStringTask;
}
...

A good alternative for this is using a CancellationTokenSource which takes a delay where you can pass in what your time span is. The “Token” is passing in as an optional parameter in the LongTaskService method where the token is implemented.

I’m just passing it into the “Task.Run” because “CancellationToken” will try to cancel the task. Then within the task itself, you can also throw it on CancellationRequested exception.

We normally see this in third-party libraries which take a TaskCancellationToken, and then you can pass it down so they can handle the cancellation. This’s really cool because you are just setting at a timeout and that is what we need.

💡 Tip: you can also manually cancel tasks. Read more here.

Fire and Forget Long Running Task

Often in application development you want a process to call another thread and continue the process flow, without waiting for a response from the called thread. This pattern is called the “fire and forget” pattern.

Task.Run() was introduce for making easier developer life. It’s a shortcut.  In fact, Task.Run is actually implemented in terms of the same logic used for Task.Factory.StartNew, just passing in some default parameters.

The issue in the example below is we are running a long task, and whatever thing can happen and that can affect the performance of our app.

...
private void MyMethod()
{
     ...
     //Fire and forget long running task using Task.Run 
     Task.Run(() => FireAndForgetLongTask());
     ...
}
...

⚠ Note: The code shown is for example purposes only. Real fire and forget tasks should always have a cancel mechanism and exception handling!!

A better way to manage this is by using Task.Factory.StartNew that give us more advanced control over our tasks.

...
private void MyMethod()
{
     ...
     //Fire and forget long running task using Task.Factory.StartNew(()=>{}, TaskCreationOptions.LongRunning)
     //Reference: https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskcreationoptions?view=netframework-4.8
     Task.Factory.StartNew(() => FireAndForgetLongTask(), TaskCreationOptions.LongRunning);
     ...
}
...

In this case, we use TaskCreationOptions to get control of how the task behaves. When we set the option to LongRunning, we are saying “I allow this task to create more threads than the available number in order get it complete it“.

TaskCompletionSource

TaskCompletionSources” let us make synchronous code run asynchronously. Basically, it’s a good way for us to create tasks manually with fine-grained control over its lifetime. 

Here what you need to know is that TaskCompletionSource<T> represents a future result and gives an ability to set the final state of the underlying task manually by calling SetCanceledSetException or SetResult methods.

Let’s take a look at the example below with a bad implementation I have seen recently.

As you can see, we are calling the TaskService but the task, before it completed, it’ll throw an exception by ThrowAnExeption method but this method is not taken into account because is forgotten in the background. Even though the exception is thrown you can’t see it. So, how we handle that?

...
/* VM Context */
...
private async Task MyMethod()
{
     ...
     try
     {
        var taskResult = await service.TaskService();
     }
     catch (Exception ex)
     {
         //This block will never be reached
     }
     ...
}
...
...
/* Service Context */
...
public Task<string> TaskService()
{
     var taskCompletionSource = new TaskCompletionSource<string>();
     var tcsTask = taskCompletionSource.Task;

     var internalTask = Task.Run(() =>
     {
         var taskResult = ThrowAnException(); //This doesn't take into account method can throw an exception so it appears to be "swallowed"
         taskCompletionSource.TrySetResult(taskResult);
     });

     return internalTask; //Bad! Example purpose.
}
...

So, we have a Task.Run where we set the result. This is almost the most important thing with the TaskCompletionSource because if you never set the result, the task never completes because we never set it to throw an exception so it might run forever if it doesn’t fault.

On the other hand, in the example above we are returning the internalTask, the task directly. This means that you TaskCompletionSource.Task is useless in your code because you are not using it.

Lets take a look how to fix this.

...
/* VM Context */
...
private async Task MyMethod()
{
     ...
     try
     {
         var taskResult = await service.TaskService();
     }
     catch (Exception ex)
     {
         //This block will be reached
     }     
     ...
}
...
...
/* Service Context */
...
public Task<string> TaskService()
{
     var taskCompletionSource = new TaskCompletionSource<string>();
     var tcsTask = taskCompletionSource.Task;

     var internalTask = Task.Run(() =>
     {
         try
         {        
              var taskResult = ThrowAnException();
              taskCompletionSource.TrySetResult(taskResult);
          }
          catch (Exception ex)
          {
              // Do something here...
              taskCompletionSource.TrySetException(ex);
          }
     });

     return tcsTask ; //Good!
}
...

To fix that, what we do is in our new method TaskService in the Task.Run itself, instead of just setting the result, we actually have a try/catch that set “TrySetException” that handle the cases in which it can possibly go wrong. In this way, we’re totally safe.

Xamarin.Forms

Updating UI properties

For this example, imagine that we’re in the code behind because we want to update “StatusLabel.Text” directly. We’re waiting with ConfigureAwait(false) because we get off the UI thread.

🚨 Note: This is for example purposes only. Don’t use ConfigureAwait(false) and then BeginInvokeOnMainThread in the same context. And of course, use your VM to manage your services, commands, and bindings.

...
private async void MyButtonClicked(object sender, EventArgs e)
{
     ...
     //If there was no configure await, execution would continue on the UI thread
     var taskResult = await service.LongTaskService().ConfigureAwait(false);

     Device.BeginInvokeOnMainThread(() =>
     {
         StatusLabel.Text += "Updated StatusLabel from UI thread!";
     });
     ...
}
...

If we use this code without BeginInvokeOnMainThread, what happens afterward if we update StatusLabel.Text directly? we get an exception that says “Only the original thread that created the view hierarchy can touch its views.”.

How to fix this? All we do is use Device.BeginInvokeOnMainThread and then put our code in there. And that’s it. Anything inside there would be on the main thread.

💡 Tip: Be sure to be on the main thread if you want to update your UI directly.

Async Task On Startup

The constructor in your App.xaml.cs will be run before your application is shown on the screen when you start up your Xamarin.Forms Application. As you know, constructors currently don’t have the ability to await async methods.

Let’s see how we can manage this depending upon your exact situation.

...
public App()
{
     ...
     Task.Run(async ()=> { await MyMethod(); });
     ...
}
...
//OR
...
protected override async void OnStart()
{
    // Handle when your app starts
}
...

If you do not care about the result of your async Task, you just want to run it, you can do it in the constructor as we show in the first example above. What this does is push your Task into a background thread.

However, it would be recommended to actually place it in the OnStart method. Add the async keyword in there. Since OnStart is just an event, and nothing is waiting for its return, using the async void is acceptable here.

💡 Tip: You can apply the same concept to a constructor Page or use the OnAppearing method in your code behind to start loading data on the page when the user is there. In the same way, following good practices some MVVM Frameworks help you with some abstraction in order to do that from your VM.

Plugins, extensions, and much more

Xamarin.Essentials: MainThread

The MainThread class in Xamarin.Essentials allow applications to run code on the main thread of execution, and to determine if a particular block of code is currently running on the main thread.

For more information, please see the following link.

AsyncAwaitBestPractices

Extensions for System.Threading.Tasks.Task that help you to safely fire and forget Task or ValueTask and Ensures the Task will rethrow an  Exception.

It also helps you Avoids memory leaks when events are not unsubscribed and allow you to use AsyncCommand in order to work with async Task safely.

⚠ Note: Most MVVM framework manages their own implementation of ICommand, be sure that they dont manage the async Task with it before using this.

For more information, please see the following link.

Sharpnado.TaskMonitor

TaskMonitor is a task wrapper component helping you to deal with “fire and forget” Task (non awaited task) by implementing async/await best practices.

It offers:

  • Safe execution of all your async tasks: taylor-made for async void scenarios and non awaited Task
  • Callbacks for any states (canceled, success, completed, failure)
  • Default or custom error handler
  • Default or custom task statistics logger

For more information, please see the following link.

More to read

  • [Video] Best Practices – Async / Await | The Xamarin Show
  • [Blog Post] Long Story Short: Async/Await Best Practices in .NET
  • [Blog Post] Getting Started with Async / Await
  • [Blog Post] C# Developers: Stop Calling .Result
  • [Blog Post] C# Async fire and forget
  • [Blog Post] Task.Run vs Task.Factory.StartNew
  • [Docs] Async/Await – Best Practices in Asynchronous Programming
  • [Source Code] async-await Xamarin Scenarios

I hope this can be helpful to you. If you know any other practices or plugin that you may recommend, you can leave it in the comments.😉

For more up-to-date content, follow me on Instagram and LinkedIn! Thank you for reading!🚀

What do you think of this content?
 
Luis Matos

Luis Matos

I help professionals and companies to create value solutions. I am a Systems Engineer, blockchain executive, and international mobile application speaker. Founder of the Malla Consulting Agency and several international technology communities.
Subscribe
Notify of
2 Comments
Oldest
Newest
Inline Feedbacks
View all comments
2
0
Would love your thoughts, please comment.x
()
x

Search in the site