Utilize asynchronous programming in C# to achieve a more responsive bot
Here you can find how to use asynchronous programming to have your data fetched from a database before you need to process them, making your bot feel more responsive. We will be using the code from this post about CosmosDB in bot framework written in C#.
First let’s see what async and await operators do.
Async
The async modifier indicates that a method can run asynchronously. However an async method runs synchronously until it reaches its first await expression, at which point the method is suspended until the awaited task is complete. In the meantime, control returns to the caller of the method. If the method that the async keyword modifies does not contain an await expression or statement, the method executes synchronously, if it does, the method will run asynchronously after it reaches the await operator. You can learn more about async here.
Await
You can use the await operator when calling an async method. The await operator suspends evaluation of the enclosing async method until the asynchronous operation completes. When the asynchronous operation completes, the await operator returns the result of the operation, if any. When the await operator is applied to the operand that represents an already completed operation, it returns the result of the operation immediately without suspension of the enclosing method. The await operator doesn’t block the thread that evaluates the async method. When the await operator suspends the enclosing async method, the control returns to the caller of the method. Learn more about await here.
Code
This is the code we used in the Cosmos DB post in order to read data from the database. To summarize, in line 5 we use the ReadAsync function to get the data. Because of the use of await we need to wait for the database to respond in order to proceed to line 6. The reason we used await in the first place is because we need the data in the very next line of code (line 6).
// Fetch data from DB DemoClass databaseValue = new DemoClass(); try { var cosmosDbResults = await CosmosDBQuery.ReadAsync(new string[] { findId }, cancellationToken); if (cosmosDbResults.Values.FirstOrDefault() != null) databaseValue = (DemoClass)cosmosDbResults.Values.FirstOrDefault(); } catch (Exception e) { await stepContext.Context.SendActivityAsync($"Error while connecting to database.\n\n{e}"); }
Let’s see now how you can improve this code with the use of Tasks. The Task class represents a single operation that does not return a value and that usually executes asynchronously. You can find more about Tasks here.
The key is, you do not have to start asking for the data right before you need to use them. In reality you can request the data from the database when you have findId. For example, in bot framework, if you need to fetch the data of the user interacting with the bot, you can have access to the user ID right from the welcome message and you can sent a request to fetch the data from the very begining of the conversation.
You should include this line in your project (in the .cs file tha uses Tasks), if you do not already have it.
using System.Threading.Tasks;
Declare the Task at the begining of your project. The type of the Task should be the same as the return type of the function that it uses, in this example the ReadAsync. The return type of ReadAsync is IDictionary<string, object>.
public static Task<IDictionary<string, object>> ReadFromDb;
When you have findId you can immediately sent a request to the database to start fetching the data. This is easily done using the ‘=’ opperator. You assign the asynchronous function to a Task, and you will get the results when the Task is completed.
ReadFromDb = CosmosDBQuery.ReadAsync(new string[] { findId }, cancellationToken);
Finally, in line 5 we will be awaiting for the Task to complete. If it is already completed we will get our results immediately. Since the task started working asynchronously on the request, before the data was needed, we diminished the time the user might need to wait for the data to be shown or used.
// Fetch data from DB DemoClass databaseValue = new DemoClass(); try { var cosmosDbResults = await ReadFromDb; if (cosmosDbResults.Values.FirstOrDefault() != null) databaseValue = (DemoClass)cosmosDbResults.Values.FirstOrDefault(); } catch (Exception e) { await stepContext.Context.SendActivityAsync($"Error while connecting to database.\n\n{e}"); }
That is how you can utilise Tasks to make you project feel more responsive to the user.