I am trying to create a HttpClient() to collect information from a RestAPI in C#. So far it works fine when I test it in a console app everything goes smoothly.
Now that I have started implementing it in Rhino it also works, BUT I constantly get a notification that the action cannot be completed because the other program is busy.
I understand that this comes from the async task that is running when fetching data from the http-client.
When switching to everything is fine, also when retrying. But the user (nor me) should always have to do this click.
Any solutions how to avoid this?
Can you post a simple sample of your code? My guess is you are waiting for the result of the Http call on the UI thread, like Nathan said. It should be relatively simple to fix.
public async Task<HttpClient> GetConnection(string password)
{
HttpClient client = null;
using (client = new HttpClient())
{
if (client.BaseAddress == null)
client.BaseAddress = new Uri(Domain);
string username = System.Environment.UserName;
// Prepare the session request body
var requestBody = new { username = username, password = password };
var content = new StringContent(
Newtonsoft.Json.JsonConvert.SerializeObject(requestBody),
Encoding.UTF8,
"application/json");
// Create a session by posting to the auth endpoint
var authResponse = await client.PostAsync("/rest/auth/1/session", content);
if (authResponse.IsSuccessStatusCode)
{
// Parse the response to get the session info
var responseBody = await authResponse.Content.ReadAsStringAsync();
var session = JObject.Parse(responseBody)["session"];
var sessionName = session["name"].ToString();
var sessionValue = session["value"].ToString();
// Add the session cookie to subsequent requests
client.DefaultRequestHeaders.Add("Cookie", $"{sessionName}={sessionValue}");
}
else
{
Rhino.RhinoApp.WriteLine($"Auth request failed: {authResponse.StatusCode}");
Rhino.RhinoApp.WriteLine(await authResponse.Content.ReadAsStringAsync());
}
}
Rhino.RhinoApp.WriteLine((client == null).ToString());
Rhino.RhinoApp.WriteLine(client.BaseAddress.ToString());
return client;
}
public async void GetIssues(string password)
{
if (Client == null)
{
Client = GetConnection(password).Result;
}
}
Of course I would be more than happy if you are able to tell me what’s wrong…
@fraguada
When I am home I will of course test InvokeAndWait though I have to figure out how, because my pracice ont his is VERY dusty .
In GetIssues, change Client = GetConnection(password).Result to Client = await GetConnection(password);
This will stop the UI thread from waiting on the result and instead awaiting the result. Although it sounds very similar, what happens under the hood is very different. Waiting (which is the same as .Result) will block the UI thread, awaiting will yield execution back to the thread while the IO request takes place, so the thread is free to process other things, like UI events.
Then there are some minor other things I would do:
Make sure to wrap the Http call in a trycatch so that you can catch any exceptions that happen when the request cannot be completed, otherwise the exception could bubble up and maybe even cause Rhino to crash.
Really minor but in C#, it is customary for async methods to end with Async, so changing both methods to end with Async is useful if this code is ever used by other devs.
I also just noticed that you are returning the HttpClient from GetConnection. That’s probably not what you want, since you are utilizing the using keyword, that client will be disposed by the time it is returned to the caller. You probably want to return whatever information you want to retrieve from the remote server (Like maybe a JObject or a string).
Thanks, @aj1.
My idea was that I would like to use GetConnection in several methods, e.g. GetIssues or CreateIssues, so it would be easier for me to establish this in one method.
Is there now way to return a client?
Otherwise I would have to use the same code block I use in GetConnection in every method, right?
There are certainly many ways to accomplish what I think you are trying to do, and I can’t get into too much implementation details here with your specific example.
Presumably though, the payload on the HTTP requests for GetIssues and CreateIssues would be different, so you’d have to create a different HTTP request anyways for each method.
And while you can return an HttpClient instance from a method, you’d then be responsible for disposing it somehow, or you could cause a resource leak at a lower level.
So, to avoid all that headache, I would just create a new HttpClient every time you make an HTTP request and wrap it around a using statement like you already do. If you want to write helper methods, have those methods return whatever information you need from the remote server, like a string or an object.
Everything works fine now, the only problem I have is to get the content out of the method properly.
When I design it as a void: perfect.
When I try this:
public async Task<List<JiraIssue>> GetIssues(string password, List<string> keys)
{
...
return issues // list of items I created before
}
I get the same popup like before.
As mentioned: when I print the content inside the void: perfect.
Does this have to do with your comment here?
Really minor but in C#, it is customary for async methods to end with Async, so changing both methods to end with Async is useful if this code is ever used by other devs.
Well, how are you calling GetIssues now? If at any point you block on that method, that is no good. In typical IO async code, you want to make sure you never call .Result or .Wait() in the entire call stack, or you’ll block the current thread.
The way this is normally done in a UI event-driven app like Rhino is to have the topmost method in the stack (What is triggered when you click on a button for example) be async void, and everything under that returns some sort of Task object.
@aj1
So much to learn for me.
I understand. I also understand that it is hard to understand what I am doing from remote, but let me try to explain.
When I run the request via GetIssues everything is fine and I will get the result of public async Task<List<Issue>> GetIssues.
As you said once I try to access the request’s Result I get the message again.
The way this is normally done in a UI event-driven app like Rhino is to have the topmost method in the stack (What is triggered when you click on a button for example) be async void , and everything under that returns some sort of Task object.
Sorry if I am a bit slow understanding this, but I really have no clue how this would be applied in my situation.
The result of this public async Task<List<Issue>> GetIssues() would be a List accessible by the Task’s result?
Hey, there was a time all this was gibberish to me too!
Here’s some pseudo-code of what I’m talking about. Notice that I never call .Result at all.
public async void ClickButtonAsync()
{
try
{
Rhino.RhinoApp.WriteLine("Getting issues...");
//You could also show some fancy UI here instead.
List<Issue> issues = await GetIssuesAsync();
//Do something with the issues here.
}
catch (Exception e)
{
//Show the exception to the user somehow.
}
}
public async Task<List<Issue>> GetIssuesAsync()
{
using HttpClient = new();
using HttpResponseMessage response = await client.GetAsync("SOME URL HERE");
List<Issue> issues;
//Somehow turn the HttpResponse into your list of issues here. Don't use .Result.
return issues;
}