Skip to main content
  1. About
  2. For Teams
Asked
Viewed 1k times
0

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);
        }
    }
}
9
  • 3
    Most likely a deadlock. Why not make TestSSN() async as well? Then just await the call instead of using .Result.
    David
    –  David
    2018-01-12 19:21:15 +00:00
    Commented Jan 12, 2018 at 19:21
  • 2
    Make TestSSN async and have it return a Task<ActionResult>. And use the await keyword instead of accessing .Response. And don't take an exception, grab the exception's message and then throw a new exception. You'll lose valuable information that way. Just remove that try/catch altogether.
    mason
    –  mason
    2018-01-12 19:21:55 +00:00
    Commented Jan 12, 2018 at 19:21
  • 3
    Side note: Your catch blocks are throwing away useful exception information and replacing it with less information. You should really just get rid of those entirely.
    David
    –  David
    2018-01-12 19:22:07 +00:00
    Commented Jan 12, 2018 at 19:22
  • 1
    By the way, you should never read Task.Result unless you know what you're doing. If you have code that ensures you're in a setting where the task has already completed, then yes, but otherwise, no. Most likely you have a deadlock situation on your hands. Additionally, your catch block should just be removed, let the original exception propagate up the call stack instead. You're throwing away stack trace, exception type, any additional properties, etc.
    Lasse V. Karlsen
    –  Lasse V. Karlsen
    2018-01-12 19:22:08 +00:00
    Commented Jan 12, 2018 at 19:22
  • 2
    Have a read over Async/Await - Best Practices in Asynchronous Programming.
    mason
    –  mason
    2018-01-12 19:27:01 +00:00
    Commented Jan 12, 2018 at 19:27

3 Answers 3

5

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.

Sign up to request clarification or add additional context in comments.

5 Comments

Thank you so much for the solution (and even more importantly the explanation). It worked like a charm!
David, why are the catch blocks "throwing away useful info". I always thought using them is a best practice.
@DeanFriedland: For starters, you’re not using them for anything. Putting code in that does nothing useful just because you feel like you’re supposed to is never best practice. In this particular case, you’re stripping out all of the original exception and replacing it with a new one. Only the message is retained. The original exception type, stack trace, any inner exceptions, any other properties of the original exception... lost. Just remove these try/catch structures entirely, they do only harm and nothing good.
Oh wow. So is the try/catch useful in any circumstances? Is there a better way to do error handling? I am almost 2 years into my career as a developer and feel like this is super important to know now before I pick up bad habits. I really appreciate the feedback.
A try/catch structure is certainly useful for error handling. But in your code above you’re not actually handling the error in any way. If you have some way of handling the error, do that. But don’t catch an exception if you’re not going to handle it.
3

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;
}

1 Comment

Thank you so much Camilo. I am going to try this as well.
0

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);
        }
    }
}

3 Comments

If you can leave a kindly comment, I will be glad to improve my answers.
Sure. I appreciate the response and the help you provided!
@DeanFriedland, thanks. But I told it to downvoter :).

Your Answer

Post as a guest

Required, but never shown

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.

Morty Proxy This is a proxified and sanitized view of the page, visit original site.