I am trying to call an async method from a synchronous method and it keeps bombing on the call to GetUsTraceApiHealth() but with no errors. What is the problem?
Calling Method:
public ActionResult TestSSN()
{
try
{
var apiResponse = GetUsTraceApiHealth().GetAwaiter().GetResult();
string responseBody = apiResponse.Content.ReadAsStringAsync().Result;
return Json(responseBody, JsonRequestBehavior.AllowGet);
}
catch (Exception e)
{
throw new Exception(e.Message);
}
}
Method Being Called:
public async Task<HttpResponseMessage> GetUsTraceApiHealth()
{
using (HttpClient httpClient = new HttpClient())
{
try
{
string uri = $"https://trace.{ConfigHelper.SterlingDomain}health?deep";
HttpResponseMessage apiResponse = await httpClient.GetAsync(uri);
return apiResponse;
}
catch (Exception e)
{
throw new Exception(e.Message);
}
}
}
Follow the async mantra of "async all the way down". Basically, you should almost never call .Result on a task. In the majority of cases, your calling method should also be async. Then you can simply await the result of the operation:
public async Task<ActionResult> TestSSN()
{
//...
var apiResponse = await GetUsTraceApiHealth();
string responseBody = await apiResponse.Content.ReadAsStringAsync();
//...
}
It should be up to the application host at the top level (in this case ASP.NET and the web server) to handle the synchronization context. You shouldn't try to mask an asynchronous operation as a synchronous one.
Simplified version of your code:
public async Task<ActionResult> TestSSN()
{
var apiResponse = await GetUsTraceApiHealthAsync();
return Json(apiResponse, JsonRequestBehavior.AllowGet);
}
public async Task<string> GetUsTraceApiHealthAsync()
{
using (HttpClient httpClient = new HttpClient())
{
string uri = $"https://trace.{ConfigHelper.SterlingDomain}health?deep";
return apiResponse = await httpClient.GetStringAsync(uri);
}
}
There's no reason to return the HttpResponseMessage to read its content as string, just use GetStringAsync.
Also, never catch an exception just to rethrow it. If you need to do that, use:
catch(Exception ex)
{
//log or whatever
throw;
}
You shouldn't mix the async and sync operations together. Proper way to perform it is decorating your methods as async and simply using await;
public async Task<ActionResult> TestSSN()
{
try
{
var apiResponse = await GetUsTraceApiHealth().GetAwaiter().GetResult();
string responseBody = await apiResponse.Content.ReadAsStringAsync().Result;
return Json(responseBody, JsonRequestBehavior.AllowGet);
}
catch (Exception e)
{
throw new Exception(e.Message);
}
}
If you don't able to apply async in the all paths, you could use ConfigureAwait to prevent deadlock.
public async Task<HttpResponseMessage> GetUsTraceApiHealth()
{
using (HttpClient httpClient = new HttpClient())
{
try
{
string uri = $"https://trace.{ConfigHelper.SterlingDomain}health?deep";
HttpResponseMessage apiResponse = await httpClient.GetAsync(uri).ConfigureAwait(false);
return apiResponse;
}
catch (Exception e)
{
throw new Exception(e.Message);
}
}
}
Related
I am handing HttpRequestException when I use PostAsync and it works fine, but when I am trying to handle same exception on GetAsync it throws TaskCanceledException a task was cancelled with a long timeout instead. How do I make GetAsync throw HttpRequestException?
public async Task<bool> AddQrCodeToRequest(int projectId, int requestId, string code, string token)
{
var data = JsonConvert.SerializeObject(new { code });
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var content = new StringContent(data, Encoding.UTF8, "application/json");
var result = await client.PostAsync(url, content);
if (result.IsSuccessStatusCode)
{
return true;
}
else
{
throw new Exception(CreateExceptionDescription(await result.Content.ReadAsStringAsync()));
}
}
public async Task<List<string>> GetUpdatedQrCodesList(Request request, string token)
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var result = await client.GetAsync(url);
if (result.IsSuccessStatusCode)
{
var requestsJson = await result.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<List<string>>(requestsJson);
}
else
{
throw new Exception(CreateExceptionDescription(await result.Content.ReadAsStringAsync()));
}
}
handling post
try
{
string QrCode = result.Text;
if (await restService.AddQrCodeToRequest(Request, result.Text, Vars.User.Token))
{
QrCodes.Add(QrCode);
await DisplayAlert("Code added", QrCode, "OK");
}
}
catch (Exception ex)
{
if (ex is HttpRequestException)
{
//network ex handling
}
else
{
//other handling
}
}
handling get (app crashes after timeout)
try
{
UpdatedQrCodes = await restService.GetUpdatedQrCodesList(Request, Vars.User.Token);
}
catch (Exception ex)
{
if (ex is HttpRequestException)
{
//never thrown
}
else
{
//never also thrown
}
}
As a workaround use nuget Xamarin.Essentials and before you execute your GET check if there's internet available:
var current = Connectivity.NetworkAccess;
if (current == NetworkAccess.Internet)
{
// Connection to internet is available
}
I am not familiar with threading in .NET.
I have an ansync method MyTest:
public async Task MyTest() {
using (HttpClient httpClient = new HttpClient()) {
httpClient.BaseAddress = new Uri(_uri);
var response = await httpClient.GetAsync("API/GetData");
if(response!=null && response.IsSuccessStatusCode) {
var json = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<Dictionary<string, string>>(json);
}
}
}
The problem that I am running into is calling the method to get the results (Dictionary).
When I step though my code I am seeing the IsCompleted is done before the results come back from my rest call.
How do I properly use threading in this case?
My method to call the async method.
public void GetTestData()
{
try
{
ARestService rest = new ARestService();
Task tsk = new Task(rest.MyTest);
if (tsk.IsCompleted)
{
var tst = "Done?";
}
}
catch(Exception ex)
{
string a = ex.Message;
}
}
If you can turn your GetTestData method into an async method simply do this.
public async Task GetTestData()
{
try
{
ARestService rest = new ARestService();
await rest.MyTest();
var tst = "Done?";
}
catch(Exception ex)
{
string a = ex.Message;
}
}
You should also define that your method returns a Task<Dictionary<string, string>> to receive the result of your rest service call, with the following statement.
Dictionary<string, string> dict = await rest.MyTest();
If not, have a look at some workarounds, like using GetAwaiter().GetResult() but as explained in this question Is .GetAwaiter().GetResult(); safe for general use? it can cause some problems, so the best option is to make your calling code async too.
I'm working on an async http call using HttpClient. The call is made inside an async task. The call is successful and I get a response from the Http call. But when I try to return the response from the task nothing happens, even though I have a breakpoint waiting after the return.
public void ExecuteTask(Foundation.Security.SecurityToken token, Order order)
{
ExecuteTaskAsync(token, order).Wait();
}
public async Task ExecuteTaskAsync(Foundation.Security.SecurityToken token, Order order)
{
if (order != null)
{
log.Info("Starting export of order " + order.ID.ToString());
bool success = await ExportOrder(order, token);
if (!success)
{
log.Error("Failed to export order with ID " + order.ID.ToString());
}
}
}
private async Task<bool> ExportOrder(Order order, Foundation.Security.SecurityToken token)
{
try
{
ResponseObject response = await webService.SendOrder(new SenderInformation(token), new ReceiverInformation(order, token));
if (response.Success && response.Status.Equals("201", StringComparison.OrdinalIgnoreCase))
{
log.Info(String.Format("Order ({0}) was successfully exported"), order.ExternalOrderID);
return true;
}
return false;
}
catch (Exception e)
{
log.Error(String.Format("Exception occured while exporting order ({0})", order.ID), e);
return false;
}
}
Below is the code which does the actual http call. I marked the last functional line with the comment "The code successfully reach this line. After this nothing happens"
public Task<ResponseObject> SendOrder(SenderInformation sender, ReceiverInformation receiver)
{
OrderRequest request = new OrderRequest(sender, receiver);
return ExecuteRequest<OrderRequest, ResponseObject>(request);
}
private async Task<ResponseType> ExecuteRequest<RequestType, ResponseType> (RequestType request)
where RequestType : RequestObject
where ResponseType : class, ResponseObject, new()
{
try
{
using (var client = new HttpClient())
{
string xml = SerializeRequest(request);
HttpContent content = new StringContent(xml);
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("text/xml");
string requestUrl = "URL";
HttpResponseMessage response = await client.PostAsync(requestUrl, content).ConfigureAwait(false);
// Parse response
if (response.IsSuccessStatusCode)
{
Stream responseStream = await response.Content.ReadAsStreamAsync();
ResponseType responseObject = DeserializeResponse<ResponseType>(responseStream);
if (responseObject != null)
{
responseObject.Success = true;
return responseObject; //The code successfully reach this line. After this nothing happens
}
else
{
log.Error("Response could not be deserialized");
}
}
else
{
log.Error("Error during request, got status code " + response.StatusCode);
}
}
}
catch (Exception e)
{
log.Error("Something went wrong!", e);
}
return new ResponseType() { Success = false };
}
The problem is on this line:
ExecuteTaskAsync(token, order).Wait();
This causes a deadlock: the awaits in the called method can't resume because the UI thread is blocked.
When you use async code, you must use it all the way; never wait synchronously for an async task to complete.
Are the following two methods, getData1Async() and getData2Async() are essentially the same? If so why don't I need EnsureSuccessStatusCode() in getData2Async() method?
class Program
{
static void Main(string[] args)
{
try
{
string uri = "https://www.blahblah.com/getdata";
Task<string> x = getData1Async(uri);
System.Diagnostics.Debug.WriteLine(x.Result);
Task<string> y = getData2Async(uri);
System.Diagnostics.Debug.WriteLine(y.Result);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
static async Task<string> getData1Async(string uri)
{
HttpClient client = new HttpClient();
HttpResponseMessage response = await client.GetAsync(uri);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
static async Task<string> getData2Async(string uri)
{
var httpClient = new HttpClient();
return await httpClient.GetStringAsync(uri);
}
}
getData1Async - here you are getting the object of type HttpResponseMessage and if you don't ensure that response has completed successfully and call response.Content.Read..., the answer will be indeterministic.
getData2Async - directly calls httpClient itself to get the string which internally makes sure that it only returns when data has been received.
My problem is the following:
On the UI thread I have a button event, from where I call a service method:
private async void RefreshObjectsButton_Click(object sender, EventArgs e)
{
var objectService = new ObjectService();
var objects = await objectService.GetObjects(UserInfo.Token);
}
The service method is:
public class ObjectService : ServiceClientBase, IObjectService
{
public async Task<ObservableCollection<ObjectViewModel>> GetObjects(string token)
{
var response = await GetAsync<ObservableCollection<HookViewModel>>("uri_address", token);
return response;
}
}
And the GetAsync method which is implemented in ServiceClientBase:
public async Task<T> GetAsync<T>(string uri, string token)
{
using (var client = CreateClient())
{
try
{
HttpResponseMessage response = new HttpResponseMessage();
response = await client.GetAsync(uri);
T retVal = default(T);
if (response.IsSuccessStatusCode)
{
retVal = await response.Content.ReadAsAsync<T>();
}
return retVal;
}
catch (Exception ex)
{
//TO DO log exception
return default(T);
}
}
}
When execution reaches GetAsync<T>(), the request is sent, I get a result and retVal contains a list of values. However after GetAsync<T>() execution ends, GetObjects() will not continue its execution anymore. I believe it's a deadlock as explained here. However using the advices seen in the previous link didn't resolve my issue. It's clearly I'm missing something here.
Can someone explain why is this deadlock happening, and maybe provide some further advice in resolving this issue?