Overcoming the Pitfalls of Async/await Part 1
What will you learn in the blog: I have this topic Asynchronous Programming in C# in my mind since long and I have been giving some small talks on Async/await in my organization. So I thought why not collect and summarize all the awesome knowledge stuff and the pain stuff as well which I went through in one of my projects while using Async in C# and blog about it for the community feedback.
So let’s get started and check out the awesome demos and diagrams which I have brought for you from books and blogs that I have read and other practical implementations. Before going further, I hope you are a beginner, intermediate or expert C# developer.
Before diving in:
As a Software Developer, we are either coding or attending daily standup calls or weekly status meeting all throughout our day usually 9-5 than sometimes when we are learning our pieces of stuff i.e. creating a Proof of Concept, preparing demos for the session. We are primarily using C# for all the coding stuff when we are coding something in .NET. Frankly speaking, we have been writing synchronous code i.e. code which executes line by line but’s it time to change this and enter into the era of Asynchronous programming model and yes it’s necessary. In the upcoming article, I will share all the important news, tweets and blog to make sure to push you guys towards Asynchronous model. Let’s begin with problems first why should I care with real use case study to implement.
Case Study: We want to retrieve User information from the API and show it to the User in a Windows Form application.
Requirement:
- Show loader to the user while calling API to make UI more response.
- Show the user list in the Grid
Dependencies:
REST API link and response:
API Endpoint: https://reqres.in/api/users?page=2
Response:
{"page":2,"per_page":3,"total":12,"total_pages":4,"data":[{"id":4,"first_name":"Eve","last_name":"Holt","avatar":"https://s3.amazonaws marcoramires/128.jpg"},.com/uifaces/faces/twitter/
{"id":5,"first_name":"Charles","last_name":"Morris","avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/stephenmoon/128.jpg"},
{"id":6,"first_name":"Tracey","last_name":"Ramos","avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/bigmancho/128.jpg"}]}
Developer Thinking:
Implementation part:
UI Screen Design:
So I have to User details in my grid so I am just consuming “data” i.e. Users part from the JSON.
Underlining synchronous code which I have written to call the REST API.
using CommanLibrary; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.IO; using System.Linq; using System.Net; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace FakeApi { public partial class Form1 : Form { public Form1() { InitializeComponent(); dataGridView1.Hide(); pictureBox1.Hide(); } private void button1_Click(object sender, EventArgs e) { pictureBox1.Show(); var httpWebRequest = (HttpWebRequest)WebRequest.Create($"https://reqres.in/api/users?page=2"); httpWebRequest.Method = "GET"; var response = (HttpWebResponse)httpWebRequest.GetResponse(); var responseString = new StreamReader(response.GetResponseStream()).ReadToEnd(); var responseObject = JsonConvert.DeserializeObject(responseString); dataGridView1.DataSource = responseObject.data; DataGridViewColumn column = dataGridView1.Columns[3]; column.Width = 300; dataGridView1.Show(); pictureBox1.Hide(); } } }
Run the Application:
Demo Feedback:
- UI not responsive
- The application is at hang state until the records are shown in Table.
- Loader not showing
Result:
Requirement Not closed by Client
Reason for failure:
In windows programming, every Graphical User Interface has an event loop or message loop which means every event that you make using the application goes into message pump which does all the work of handling events ( Get Message, Dispatch message). So each event is executed one by one synchronously i.e. other event has to be on waiting stage till the first event gets executed.
So what happened
- When we do a click event we are trying to raise a UI event to show Picture box which is not been able to handle by message pump because it’s already executing our button click.
- So it will not handle any event until it completes the API call and binds the data in DataGrid.
- Due to this when our Thread was performing the button click event task it has frozen the UI.
What should we do now?
We should use the HttpClient Library to call the API, as a newbie developer I just got a piece of news that it has new Asynchronous API that won’t block a thread. The asynchronous code doesn’t block UI thread I just read so Wow I can use the library and implement it and solve this case study as per requirement. Consider that I don’t have any experience of Async and Await and I am using HttpClient blindly.
New Implementation with HttpClient:
using CommanLibrary; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace FakeApi { public partial class Form1 : Form { private HttpClient _client; public Form1() { InitializeComponent(); dataGridView1.Hide(); pictureBox1.Hide(); _client = new HttpClient(); } private void button1_Click(object sender, EventArgs e) { pictureBox1.Show(); var response= _client.GetStreamAsync("https://reqres.in/api/users?page=2").Result; var responseString = new StreamReader(response).ReadToEnd(); var responseObject = JsonConvert.DeserializeObject(responseString); dataGridView1.DataSource = responseObject.data; DataGridViewColumn column = dataGridView1.Columns[3]; column.Width = 300; dataGridView1.Show(); pictureBox1.Hide(); } } }
Let’s run the application now with the new implementation.
Demo Feedback:
- UI again not responsive
- Again Application is at hang state till the records are shown in Table.
- Again Loader not showing
Result:
Requirement Not closed by Client
So what I am doing wrong despite using the Asynchronous API.
Analysis:
I am implementing asynchronous API wrongly. Let’s dig in and see what I am doing wrong.
- I am using asynchronous API in a synchronous way by call .Result method on HttpClient object
var response= _client.GetStreamAsync("https://reqres.in/api/users?page=2").Result;
- I am calling asynchronous API without Async and await Pattern. So you can avoid the responsive problem with the help of Async and await and Task.
- A task is a Unit of work which we want to perform like calling an API and returning response. It can be a simple function whatever you want to code it.
- So every asynchronous method which returns a type can be a primitive type (int, string, bool etc. or complex type objects etc.)
- Use Async and await to use Asynchronous calling and avoid unresponsiveness problem.
Solving the Mistakes made while using Asynchronous API:
Converting the Calling API to a Task method:
Using an Asynchronous API from an event method which is actually void.
Actual Code implementation:
using CommanLibrary; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace FakeApi { public partial class Form1 : Form { private HttpClient _client; public Form1() { InitializeComponent(); dataGridView1.Hide(); pictureBox1.Hide(); _client = new HttpClient(); } private async void button1_Click(object sender, EventArgs e) { pictureBox1.Show(); var users = await GetUserFromApi(); dataGridView1.DataSource = users.data; DataGridViewColumn column = dataGridView1.Columns[3]; column.Width = 300; dataGridView1.Show(); pictureBox1.Hide(); } public async Task GetUserFromApi() { var response=await _client.GetStreamAsync("https://reqres.in/api/users?page=2"); var responseString = new StreamReader(response).ReadToEnd(); return JsonConvert.DeserializeObject(responseString); } } }
Now we have implemented the API call with Async and Await, lets now run the application and see whether we are able to the Customer requirement or not?
Demo Feedback:
- UI is responsive
- Loader showing
Result: Requirement closed by Customer.
Things to remember:
- The synchronous code is messy truth that we are always writing in our generations and We all “know” sync methods are “cheap”. Wait for the result before returning.
- Use Asynchronous API for calling Network bound Calls I.e. Database calls, Network bound API calls. These API was given to simplify our life to solve the unresponsiveness problem in the mobility era where Responsive is the first priority.
- Don’t block sync over Async i.e. Don’t block wait() for Result. We will discuss this point in brief in the upcoming article why this is seriously bad to use.
- Use Async and await pattern in a calling function.
- Use Task
Other Post Coming in this Series
- Async and Await in Depth what is happening inside the hood.
https://github.com/SailleshPawar/TPLExample/tree/master/FakeApi
If you want to learn async and await from start my honest suggestion would be to watch this awesome vide.
References:
https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/