Understanding async await instruction during a HttpClient request - c#

Let's say I have a solution composed of 2 projects. An old WinForm classic project. In this old project, I have a login window. On click "OK" of this login window, I start an event that will call a REST API. Both applications start at the same time in debug mode.
Somewhere in my code, I have this code:
public async Task<User> Login(string username, string password)
{
HttpResponseMessage response = await Client.GetAsync($"api/Login?login={username}&password={password}");
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
return new User();
response.EnsureSuccessStatusCode();
var userDto = await response.Content.ReadAsAsync<UserDto>();
var user = userDto.ToUser();
return user;
}
On the first line when I call the Client.GetAsync I call my API. In my API I properly receive the call and I properly return an Ok with my User object or I return another code. It works. My API works. But then nothing. My client never continues. It seems Client.GetSync waits for something. I never go on the next step where I evaluate the StatusCode.
public async Task<User> Login(string username, string password)
{
HttpResponseMessage response = Client.GetAsync($"api/Login?login={username}&password={password}").Result;
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
return new User();
response.EnsureSuccessStatusCode();
var userDto = await response.Content.ReadAsAsync<UserDto>();
var user = userDto.ToUser();
return user;
}
Same code without the await I have no problem. My code run till the next step. Proof my API is not the problem.
This is clear it is an issue related to await/async. I must do something wrong but what? Can you help me? Is it related to the debugger?
For more information here is a picture of my code before
And after I click for the next step. Note my call stack is empty and code is still running.
As requested here is the code where I call the login. I just added the Async word before the Sub and changed the _authService.Login(username, password).Result by await _authService.Login(username, password)
I works now.
Private Async Sub ButLogin_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles butLogin.Click
DataProxies.SetToken()
Dim _authService As IAuthenticationService = New AuthenticationService()
Dim username As String = txtLogin.Text
Dim password As SecureString = New NetworkCredential(String.Empty, txtPwd.Text).SecurePassword
Dim auth As Tuple(Of Boolean, User) = Await _authService.Login(username, password)
If (auth.Item1) Then
Dim user As User = auth.Item2
Name = $"{user.FirstName} {user.LastName}"
ApiInformations.ApiToken = user.SessionToken
End If
End Sub

I just added the Async word before the Sub and changed the _authService.Login(username, password).Result by await _authService.Login(username, password)
The general guidance is "Don't block on async code". This is one of the asynchronous programming best practices.
Blocking on asynchronous code is bad because await works by capturing a "context" by default, and resumes executing the async method in that context. One context is the UI context, which causes the async method to resume executing on the UI thread.
So the deadlock you were seeing was caused by blocking the UI thread. The code was calling the async method and then blocking the UI thread until that async method completed. However, the await in that async method captured the UI context, so it was waiting for the UI thread to be free before it could complete. The UI thread was waiting for the async method and the async method was waiting for the UI thread: deadlock.
The reason your fix worked is that the UI thread is no longer blocked waiting for the async method, so there's no more deadlock.

Related

WebAPI HTTP request not completing until queued work kicks off on background task

In my .Net 6 WebPI service, I am queueing work to a background task queue, very closely based on the example here, but I could post parts of my implementation if that would help:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-6.0&tabs=visual-studio#queued-background-tasks
I am running into unexpected behavior where control is not returned to the caller, even after the return Ok(..) completes in the controller. Instead the request only completes after the await Task.Delay(1000); line is reached on the queued work item. The request returns to the client as soon as this line is reached, and does not need to wait for the Delay to finish.
I'm guessing this is because of the await either starting a new async context, or someone un-sticking the async context of the original request. My intention is for the request to complete immediately after queuing the work item.
Any insight into what is happening here would be greatly appreciated.
Controller:
public async Task<ActionResult> Test()
{
var resultMessage = await _testQueue.DoIt();
return Ok(new { Message = resultMessage });
}
Queueing Work:
public TestAdapter(ITaskQueue taskQueue)
{
_taskQueue = taskQueue;
}
public async Task<string> DoIt()
{
await _taskQueue.QueueBackgroundWorkItemAsync(async (_cancellationToken) =>
{
await Task.Delay(1000);
var y = 12;
});
return "cool";
}
IoC:
services.AddHostedService<QueueHostedService>();
services.AddSingleton<ITTaskQueue>(ctx =>
{
return new TaskQueue(MaxQueueCount);
});
TaskQueue:
private readonly Channel<BackgroundTaskContext> _queue;
public TaskQueue(int capacity)
{
var options = new BoundedChannelOptions(capacity)
{
FullMode = BoundedChannelFullMode.Wait
};
_queue = Channel.CreateBounded<BackgroundTaskContext>(options);
}
public async ValueTask QueueBackgroundWorkItemAsync(
Func<CancellationToken, ValueTask> workItem)
{
if (workItem == null)
{
throw new ArgumentNullException(nameof(workItem));
}
await _queue.Writer.WriteAsync(new BackgroundTaskContext(workItem, ...));
}
Not sure what you expect here. I'm assuming you want the async method to return the cool in the api response. That's fine but because your also awaiting the async call with in DoIt(), then it pauses until QueueBackgroundWorkItemAsync finishes. You could remove the await and it will run and return as you expect.
I can't say that I'm a big fan of that design as you lose contact with it with exception of the cancellation token. Another thought would be to Send that work off to a console job or function app using message bus or even another http call.
Additional Notes:
Async can be complicated to explain because in reality, it wraps up code and executes on a thread of it's choosing. The await simulates the synchronous behavior.
await Task.Delay(1000); // Runs on it's own thread but still halts code execution for 1 second.
await _taskQueue.QueueBackgroundWorkItemAsync(async (_cancellationToken) // Waits for control to be returned from the code inside.
var resultMessage = await _testQueue.DoIt(); // Always waits for the code inside to complete.
If your wanting something to run without pausing code execution, you can either remove the await or add a Task.Run(() => { }); pattern. Is it a good idea is a whole other question. It also matters whether you need information back from the async method. If you don't await it then you'll get null back as it doesn't wait around for the answer to be computed.
This appears just to have been user error using the debugger. The debugger is switching to the background task thread and hitting breakpoints there before the response fully returns giving the appearance that control was not being returned to the client and being carried into the background task.
Even after adding some synchronous steps in QueueBackgroundWorkItemAsync and putting breakpoints on them, control was not immediately returned to the original http call. Only after I tried adding a long running task like await Task.Delay(1000); did enough time/ticks pass for the http response to return. I had conflated this with just the await somehow freeing up the original http context.

How to await the return response in order to assign to the .Text property of a label in C#

The program asks a person if he wants to check the last closing price of Bitcoin and whenever he pushes the button it should first say "Loading..." and wait until the price is received and assign it to the .Text property of the label. When I run the code in a console app the application closes before actually receiving the needed information (writes only "Loading..." ) but if I add ReadKey() the price information shows up. I guess something similar happens to the windows forms app which tries to assign a missing value to the text property of value, hence the program crashes after displaying "Loading...".
public static async Task<string> ApiCall(string apikey)
{
RestClient client = new RestClient("https://api.polygon.io/v2/aggs/ticker/X:BTCUSD/prev?adjusted=true&apiKey=");//write your api key
RestRequest request = new RestRequest($"?api-key={apikey}", Method.GET);
IRestResponse response = await client.ExecuteAsync(request);
return response.Content;
}
public static async Task<string> apiReceiver(string last_closed)
{
Task<string> apiCallTask = getAPI.ApiCall("[apikey]");
string result = apiCallTask.Result;
dynamic array = JsonConvert.DeserializeObject(result);
last_closed = array.results[0].c;
return last_closed;
}
public static async Task dataWait(Label lab, string last_closed)
{
lab.Text = "Loading info ...";
lab.Text = await apiReceiver(last_closed);
}
private async void button1_Click(object sender, EventArgs e)
{
string last_closed = "";
await getAPI.dataWait(label1, last_closed);
}
Why aren't you awaiting getAPI.ApiCall("[apikey]");? Using .Result before the task is completed, will result in a deadlock. The winforms has a SynchronizationContext set on that thread. Meaning that after the await, you're back on the UI thread and allowed and therefor able to modify UI-controls.
When you use .Result on a task, if it's not finished, it will wait there (block the thread). The problem is that when the task is ready, it will be posted on the UI thread, but never be executed, because the thread still blocked.
The difference between winforms and console. The console hasn't got a SynchronizationContext set, so the rest of the method (after the await) is posted on the threadpool. You're allowed to call Console.Writeline on anythread.
So use await here, so the thread isn't blocked.
public static async Task<string> apiReceiver(string last_closed)
{
string result = await getAPI.ApiCall("[apikey]");
dynamic array = JsonConvert.DeserializeObject(result);
last_closed = array.results[0].c;
return last_closed;
}
Here's some information:
source
However, if that code is run in a UI Application, for example when a button is clicked like in the following example:
Then the application will freeze and stop working, we have a deadlock. Of course, users of our library will complain because it makes the application unresponsive.
More about SynchronizationContext read codeproject.com Understanding-the-SynchronizationContext

The code after await operation is not executing

I'm writing a xamarin forms app and I'm using an API that I created. I've followed a tutorial to consume the Api but the code after the async operation never gets executed, it jumps out to the main function.
The code is exactly like the one in the tutorial I've been following. I didn't find any info since there is no error message.
private async void ChecarCredenciales(string username, string password)
{
HttpClient client = new HttpClient();
var url = "http://localhost:57008/api/operadores/" + username;
var response = await client.GetStringAsync(url).ConfigureAwait(false);
Lecturista = JsonConvert.DeserializeObject<Operadores>(response);
}
The JsonConvert.DeserializeObject never gets executed so the Lecturista variable never gets initialized.
Thanks in advance.
First as other already commented change your method to be async Task rather like private async Task ChecarCredenciales(string username, string password){
Second in your await block you are saying to continue on a Threadpool thread context rather on the same synchronization context by doing ConfigureAwait(false);. I would suggest you continue on the same context since on the next step you are requiring the resultant data
var response = await client.GetStringAsync(url);
Lecturista = JsonConvert.DeserializeObject<Operadores>(response);

GetAsync : not returning HttpResponseMessage

The apps should receive httpresponsemessage from LoginUser() but it becomes not responding.
private void button1_Click(object sender, EventArgs e)
{
if (LoginUser(tUser.Text, Password.Text).Result.IsSuccessStatusCode)
{
Notifier.Notify("Successfully logged in.. Please wait!");
}
else
{
Notifier.Notify("Please check your Credential..");
}
}
public async Task<HttpResponseMessage> LoginUser(string userid, string password)
{
string URI = "http://api.danubeco.com/api/userapps/authenticate";
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("c291cmF2OmtheWFs");
using (var response = await client.GetAsync(String.Format("{0}/{1}/{2}", URI, userid, password)))
{
return response;
}
}
}
Please help!
You are blocking the UI thread and causing a deadlock. From Stephen Cleary's blog (Just replace GetJsonAsync with your LoginUser method, and GetStringAsync with client.GetAsync):
So this is what happens, starting with the top-level method
(Button1_Click for UI / MyController.Get for ASP.NET):
The top-level method calls GetJsonAsync (within the UI/ASP.NET context).
GetJsonAsync starts the REST request by calling HttpClient.GetStringAsync (still within the context).
GetStringAsync returns an uncompleted Task, indicating the REST request is not complete.
GetJsonAsync awaits the Task returned by GetStringAsync. The context is captured and will be used to continue running the
GetJsonAsync method later. GetJsonAsync returns an uncompleted Task,
indicating that the GetJsonAsync method is not complete.
The top-level method synchronously blocks on the Task returned by GetJsonAsync. This blocks the context thread.
… Eventually, the REST request will complete. This completes the Task that was returned by GetStringAsync.
The continuation for GetJsonAsync is now ready to run, and it waits for the context to be available so it can execute in the context.
Deadlock. The top-level method is blocking the context thread, waiting for GetJsonAsync to complete, and GetJsonAsync is waiting for
the context to be free so it can complete.
And the simple available solutions (also from the blog):
In your “library” async methods, use ConfigureAwait(false) wherever possible.
Don’t block on Tasks; use async all the way down.
The 2nd solution suggests that you change button1_Click into:
private async void button1_Click(object sender, EventArgs e)
{
if ((await LoginUser(tUser.Text, Password.Text)).IsSuccessStatusCode)
{
Notifier.Notify("Successfully logged in.. Please wait!");
}
else
{
Notifier.Notify("Please check your Credential..");
}
}

Error: An asynchronous module or handler completed while an asynchronous operation was still pending

I have a Controller Action method to Save user Details like below.
public async Task<ActionResult> SaveUser(ViewModel.VM_CreateUser user)
{
var result = await _user.Save(userDetails);
return Json(new { Success = String.IsNullOrEmpty(result) });
}
So far there is no issue in above 4 lines function.
public async Task<ActionResult> SaveUser(ViewModel.VM_CreateUser user)
{
var result = await _user.Save(userDetails);
new MailController().CreateUser(user.userDetails); //<==This is problem line of code.
}
and Below is my Mail Controller.
public class MailController : MailerBase
{
public void CreateUser(ViewModel.VM_User user)
{
To.Add(user.EmailAddress);
From = System.Configuration.ConfigurationManager.AppSettings["emailSender"];
Subject = "Hi";
Email("CreateUser", user).DeliverAsync();
}
}
in the above code, I am facing problem when I execute the Email Sending code. I get below error message.
An asynchronous module or handler completed while an asynchronous
operation was still pending
Please suggest the corrective action !!
This is because async methods track their completion, even async void. They do this by registering with the SynchronizationContext when starting and marking the operation complete when returning. ASP.NET tracks all created operations and requires them to all be completed before your action returns, otherwise it returns an error to the HTTP client. If you need to run a “fire and forget” method, you must manually avoid launching it on the ASP.NET SynchronizationContext. For example, await Task.Run(() => CallFireAndForget()) (await so that you wait for the synchronous portion to run on the thread pool. This works because CallFireAndForget() is async void. To fire a method which returns a Task as fire-and-forget, you have to explicitly avoid returning the Task to Task.Run() like this: await Task.Run(() => { CallFireAndForgetAsync(); })).
Another way to get the same error message to show up should be to write this in an asynchronous action:
// BAD CODE, DEMONSTRATION ONLY!
AsyncOperationManager.CreateOperation();
If you use the AsyncOperation API, you have to ensure you call AsyncOperation.OperationCompleted() prior to allowing the action method to return.
In your case, if the mail sending is really intended to be a fire-and-forget task, you can do the following inside of CreateUser after changing its signature to async Task CreateUserAsync(ViewModel.VM_User user):
await Task.Run(() => Email("CreateUser", user).DeliverAsync());
and in your action:
await new MailController().CreateUserAsync(user.userDetails);
Ideally, you wouldn’t use Task.Run() for something like this. Instead, you can temporarily replace the current SynchronizationContext instead (NOTE WELL: Never await inside the try{}—instead, if you need to, store the Task in a local and await it after restoring the original SynchronizationContext (it may be safer to use a primitive such as SynchronizationContextSwitcher.NoContext() (source) which does the right thing)):
var originalSynchronizationContext = SynchronizationContext.Current;
try
{
SynchronizationContext.SetSynchronizationContext(null);
new MailController().CreateUser(user.userDetails);
}
finally
{
SynchronizationContext.SetSynchronizationContext(originalSynchronizationContext);
}
Does DeliverAsync return a Task? If it does, try this.
public class MailController : MailerBase
{
public async Task CreateUserAsync(ViewModel.VM_User user)
{
To.Add(user.EmailAddress);
From = System.Configuration.ConfigurationManager.AppSettings["emailSender"];
Subject = "Hi";
await (Email("CreateUser", user).DeliverAsync());
}
}
And then in your controller, await the task returned by CreateUserAsync.
public async Task<ActionResult> SaveUser(ViewModel.VM_CreateUser user)
{
var result = await _user.Save(userDetails);
await (new MailController().CreateUserAsync(user.userDetails));
return Json(new { Success = String.IsNullOrEmpty(result) });
}
note: If your goal is to make sending the email a background fire and forget operation, this is not it.

Categories