C Run Async Task and Continue Ui With Delay
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 justbest 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
MaybeAsync/Awaitis 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 useAsync/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, aTaskService, and all this service it's doing is running a delay internally. This might be very familiar if you're using HttpClient, for example.
TheTaskServiceis 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 theUIThread.
How this can affect my app? When we are in theUIThread, 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 callawaiton the task and make our methodasyncin 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:awaitthe first one,awaitthe second one,awaitthe 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 take3 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 aList, and then we can call "Task.WhenAll" on the whole collection.
WhenAllmeans 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 take3 seconds to complete the task. But if we run them withTask.WhenAll, we'll see that they all fired off, and then they all completed together inone 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 theUI thread and actuallyawait, 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 theawaitwas on a background thread and continue the execution on theUI thread.
In this case, your app is going to be locked by yournext intensive operation. Very similar to a.Resultbut we didn't use it we are usingawait. 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 theUI thread.
When we sayConfigureAwait(false), we are saying I allow execution to continue on abackground 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. WhenTask.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 theAwaitStringTaskAsyncmethod, we justreturn awaitinstead of returning thetaskdirectly. This can be a helper method that might be doing some stuff, but we've added anasync andawait 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 toawaittwice. 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 aTask and let the other side handle it as appropriate. Note that if we don't have return await, but return aTaskinstead, the return happens right away.
If the last sentence of your code is areturn awaityou may actually consider refactoring it so that the return type of the method isTask<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 anasyncTaskorasync Task<T> method, that exception is captured and placed on theTaskobject.
Withasync void methods, there is noTaskobject, so any exceptions thrown out of anasync void method will be raised directly on the SynchronizationContext that was active when theasync void method started.
... public async void AsyncVoidMethod() { //Bad! } public async Task AsyncTaskMethod() { //Good! } ...
Tip: consider usingasync Task instead of
async void.
In the example below the catch block inside ofThisWillNotCatchTheException() method will never be reached. But we can fix this just replacing theasync void withasync Task as you can see in theThisWillCatchTheException() 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 aTask.
Return Task inside try/catch or using block
Return Task can cause unexpected behavior used inside atry/catch block (an exception thrown by the async method will never be caught) or inside ausingblock 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 thetry/catch block, but will be caught in an outer methodgenerated 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, theReturnTaskIssueWithUsingmethod 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 thattry/catch change how exceptions are handled. Making our methodasyncwe canawaitthe result andcatchthe exception that will be thrown.
Tip: If you need to wrap your async code in atry/catch orusingblock, usereturn await instead.
Tip: Remember, the only reason you'd want to addasync/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 methodasyncwe canawaitthe result and tell the method that can't disposeResourceobject because we are waiting for the result in order to continue with the process.
Handling exceptions
With tasks, we talked about.Result,we talked aboutawait, 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 theFireAndForgetTaskbut we don't know that. This is the typical code that we just need to execute but we add atry/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'veFireAndForgetTask, 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". WhatContinueWithdoes is, once our task completes, regardless, if we awaited it or not, this continuation will happen and we can pass in anAction. In this action, we have theTaskas a parameter, so we can inspect our task.
We can also set theseContinuationOptions, where you can specifyOnlyOnFaulted,OnlyOnCanceled, and much more in order to handle those scenarios.
Tip: WithContinueWith 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, usingContinueWithwe 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 useTask.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 atimeout. 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 aCancellationTokenSource which takes a delay where you can pass in what your time span is. The "Token" is passing in as an optional parameter in theLongTaskService 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 onCancellationRequested exception.
We normally see this in third-party libraries which take aTaskCancellationToken, 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 forTask.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 acancel mechanism and exception handling!!
A better way to manage this is by usingTask.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 useTaskCreationOptionsto get control of how the task behaves. When we set the option toLongRunning, 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 SetCanceled
, SetException
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 theTaskServicebut the task, before it completed, it'll throw an exception byThrowAnExeption 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 aTask.Run where we set the result. This is almost the most important thing with theTaskCompletionSourcebecause 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 theinternalTask, the task directly. This means that youTaskCompletionSource.Taskis 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 methodTaskServicein theTask.Run itself, instead of just setting the result, we actually have atry/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 withConfigureAwait(false) because we get off the UI thread.
Note: This is for example purposes only. Don't useConfigureAwait(false) and thenBeginInvokeOnMainThread 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 withoutBeginInvokeOnMainThread, what happens afterward if we updateStatusLabel.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 useDevice.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 yourApp.xaml.cs will be run before your application is shown on the screen when you start up your Xamarin.FormsApplication. 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 yourasync 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 yourTaskinto a background thread.
However, it would be recommended to actually place it in theOnStartmethod. Add theasynckeyword in there. SinceOnStartis just an event, and nothing is waiting for its return, using theasync void is acceptable here.
Tip: You can apply the same concept to a constructorPage or use theOnAppearingmethod in your code behind to start loading data on the page when the user is there. In the same way, following good practices someMVVM Frameworks help you with some abstraction in order to do that from your VM.
Plugins, extensions, and much more
Xamarin.Essentials: MainThread
TheMainThread 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 forgetTaskorValueTaskand Ensures the Task
will rethrow an Exception
.
It also helps you Avoids memory leaks when events are not unsubscribed and allow you to useAsyncCommandin order to work withasync Task safely.
Note:Most MVVM framework manages their own implementation ofICommand, be sure that they dont manage theasync 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 implementingasync/await best practices.
It offers:
- Safe execution of all your async tasks: taylor-made for
async
void
scenarios and non awaitedTask
- 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!🚀
Source: https://luismts.com/async-await-and-task-best-practices-for-xamarin/