C
C#5mo ago
Turwaith

HttpClient does not return anything

I am currently writing a method that makes a GET request to a server. The code I'm using for that is
using (var httpClient = new HttpClient())
{
httpClient.Timeout = TimeSpan.FromSeconds(5);

httpClient.DefaultRequestHeaders.Add("licenseKey", licenseKey);
httpClient.DefaultRequestHeaders.Add("customerKey", customerKey);
string url = "http://localhost:3001/api/myTool/license/register";
HttpResponseMessage response = await httpClient.GetAsync(url);
if (response.IsSuccessStatusCode)
{
string responseBody = await response.Content.ReadAsStringAsync();
JObject json = JObject.Parse(responseBody);

return json["expiresOn"]?.ToString();
}
else
{
return null;
}
}
using (var httpClient = new HttpClient())
{
httpClient.Timeout = TimeSpan.FromSeconds(5);

httpClient.DefaultRequestHeaders.Add("licenseKey", licenseKey);
httpClient.DefaultRequestHeaders.Add("customerKey", customerKey);
string url = "http://localhost:3001/api/myTool/license/register";
HttpResponseMessage response = await httpClient.GetAsync(url);
if (response.IsSuccessStatusCode)
{
string responseBody = await response.Content.ReadAsStringAsync();
JObject json = JObject.Parse(responseBody);

return json["expiresOn"]?.ToString();
}
else
{
return null;
}
}
For the endpoint, I'm currently using Mockoon until the actual server is ready. Now my call does not return. It neither comes back with an actual response nor does it throw the TaskCanceledException which it should after timing out. I've let it run for over 2 minutes now and it never reaches the exception nor the if statement right below the call. How can that happen? Mockoon logs the call and also seems to send something back, but this never arrives apparently. I can use call the endpoint successfully using postman, so that can't be it...
13 Replies
Turwaith
Turwaith5mo ago
As additional information: I'm calling all this from the UI thread using .GetAwaiter().GetResult(). I know this is bad practice because it blocks the UI thread for the calls duration, but in this case this does not matter since the user isn't supposed to do anything anyways until the call returns something.
Pobiega
Pobiega5mo ago
Try with the same code in a console app, using a real async main Narrow down the issue Also, there is an extensionmethod to make a get request and deserialize the response as a given JSON model, iirc it's called GetAsJson
Turwaith
Turwaith5mo ago
Wait I think I solved it.... I took the .GetAwaiter().GetResult() away from the call and put it into a Task.Run(() => Call()) It now runs on a different thread and seems to work... Maybe I don't understand async programming enough to understand why this solved it... It doesn't make sense to my why the actual call itself would not return in the previous solution The problem is I NEED the UI thread to be blocked. The UI waits for the response and than closes when it was successful or shows a warning when it was not.
Jimmacle
Jimmacle5mo ago
you never want the UI thread to be blocked, that will prevent normal things like dragging/resizing the window if that remote server goes down or gets slow do you want your interface to appear like it's not responding?
Turwaith
Turwaith5mo ago
Yes. The application itself is not a UI based application, it runs only in the background. Also, the actual GUI is only used once, when the user starts the application for the first time, they need to enter a product key which is then checked using the api call. And I want this window to be blocked during the call so that it is visible that you should not do anything with it while it is checking. But I have solved it. I now have the Task.Run but with the .Wait() method attached. This blocks the UI, waits for the response AND the call does not get swallowed anymore. It works fine now But I am absolutely with you that in most cases, the UI thread should not be blocked
Jimmacle
Jimmacle5mo ago
it's really not most, it's all if the window message pump stalls windows will show it as "not responding"
jcotton42
jcotton425mo ago
Just disable the controls or show a progress bar If your UI thread is blocked it can't pump messages, which means Windows will show the app as not responding The user will then likely assume your app has crashed, and force quit it There is never ever a reason to block the UI thread It's always the result of a programming error Also, in Forms or WPF, blocking on a Task can $deadlock @Turwaith
MODiX
MODiX5mo ago
Don't Block on Async Code
This is a problem that is brought up repeatedly on the forums and Stack Overflow. I think it’s the most-asked question by async newcomers once they’ve learned the basics.
Turwaith
Turwaith5mo ago
How would I then solve this better so that I don't block the UI thread. I will give you the code that is needed.
public static class LicenseCheckCaller
{
public static bool RegisterKey(string licenseKey, string customerKey, out string expDate)
{
expDate = "";
string result = null;

Task.Run(() =>
{
result = RegisterKeyCall(licenseKey, customerKey).Result;
}).Wait();

if (result is null)
{
return false;
}
expDate = result;
return true;
}

private static async Task<string> RegisterKeyCall(string licenseKey, string customerKey)
{
try
{
using (var httpClient = new HttpClient())
{
httpClient.Timeout = TimeSpan.FromSeconds(5);

httpClient.DefaultRequestHeaders.Add("licenseKey", licenseKey);
httpClient.DefaultRequestHeaders.Add("customerKey", customerKey);
string url = "http://localhost:3001/api/myTool/license/register";
HttpResponseMessage response = await httpClient.GetAsync(url);

if (response.IsSuccessStatusCode)
{
string responseBody = await response.Content.ReadAsStringAsync();
JObject json = JObject.Parse(responseBody);

return json["expiresOn"]?.ToString();
}
else
{
return null;
}
}
}
catch (HttpRequestException e)
{
Logger.WriteLog(e.ToString());
return null;
}
catch (TaskCanceledException e)
{
Logger.WriteLog($"Request timed out: {e.Message}");
return null; // Handle timeout situation
}
catch (Exception e)
{
Logger.WriteLog($"Something went wrong: {e.Message}");
return null;
}
}
}
public static class LicenseCheckCaller
{
public static bool RegisterKey(string licenseKey, string customerKey, out string expDate)
{
expDate = "";
string result = null;

Task.Run(() =>
{
result = RegisterKeyCall(licenseKey, customerKey).Result;
}).Wait();

if (result is null)
{
return false;
}
expDate = result;
return true;
}

private static async Task<string> RegisterKeyCall(string licenseKey, string customerKey)
{
try
{
using (var httpClient = new HttpClient())
{
httpClient.Timeout = TimeSpan.FromSeconds(5);

httpClient.DefaultRequestHeaders.Add("licenseKey", licenseKey);
httpClient.DefaultRequestHeaders.Add("customerKey", customerKey);
string url = "http://localhost:3001/api/myTool/license/register";
HttpResponseMessage response = await httpClient.GetAsync(url);

if (response.IsSuccessStatusCode)
{
string responseBody = await response.Content.ReadAsStringAsync();
JObject json = JObject.Parse(responseBody);

return json["expiresOn"]?.ToString();
}
else
{
return null;
}
}
}
catch (HttpRequestException e)
{
Logger.WriteLog(e.ToString());
return null;
}
catch (TaskCanceledException e)
{
Logger.WriteLog($"Request timed out: {e.Message}");
return null; // Handle timeout situation
}
catch (Exception e)
{
Logger.WriteLog($"Something went wrong: {e.Message}");
return null;
}
}
}
And this is where this is called from my viewmodel:
private void CheckAndAdd(object o)
{
string expDate = "";
bool isValidKey = LicenseCheckCaller.RegisterKey(SecretKeyGenerator.GenerateFullSecretKey(TextBoxCustomerKey), TextBoxCustomerKey, out expDate);

if (isValidKey)
{
ConfigFileAccess.WriteToConfigFile(TextBoxCustomerKey);

// Restart application to load the changes
Process.Start(Application.ResourceAssembly.Location);

Application.Current.Shutdown();
}
else
{
LabelText = "License key invalid";
LabelColor = new SolidColorBrush(Colors.Red);
}
}
private void CheckAndAdd(object o)
{
string expDate = "";
bool isValidKey = LicenseCheckCaller.RegisterKey(SecretKeyGenerator.GenerateFullSecretKey(TextBoxCustomerKey), TextBoxCustomerKey, out expDate);

if (isValidKey)
{
ConfigFileAccess.WriteToConfigFile(TextBoxCustomerKey);

// Restart application to load the changes
Process.Start(Application.ResourceAssembly.Location);

Application.Current.Shutdown();
}
else
{
LabelText = "License key invalid";
LabelColor = new SolidColorBrush(Colors.Red);
}
}
I need the window to stay open until the call comes back or cancels and then react accordingly
jcotton42
jcotton425mo ago
If what you're trying to accomplish is informing the user they're not supposed to do anything, then just do that Show a status message, show a progress bar, disable the controls, something like that
Turwaith
Turwaith5mo ago
Yeah but how do I do that in the context of async programming? Task.Run will put the actual call into another thread, that's fine. And without the .Wait() the code will continue running immediately. So I need the window to react somehow to the request-thread returning its task. Do I fire an event from there? Or how do I make that the UI threads knows when the call is finished and has returned?
jcotton42
jcotton425mo ago
Task.Run vs BackgroundWorker: Intro
This is an introductory post for a new series that I’ll be doing comparing BackgroundWorker to Task.Run (in an async style). I always recommend Task.Run, and I have already written a long post describing why, but I still see some developers resisting the New Way of Doing Things (TM). So this will be a short series where I’ll compare the code sid...
jcotton42
jcotton425mo ago
This is about comparing to background worker, but it shows the general pattern of using a task to do background work in a GUI app just ignore the BackgroundWorker bits