1

I've a web API on server side as below. It is using async-await pattern. All these methods are present inside MyClassController class

[Route("install")]
public async Task<IHttpActionResult> InstallProduct()
{ 
   Log("milestone1");
   Install();
   Log("milestone6");
}

private bool Install()
{
   MyClass.Register();        
   Log("milestone5");
}

private static async void Register()
{
    Log("milestone2");
    using (HttpClientHandler handler = new HttpClientHandler())
    {
        using (var client = new HttpClient(handler))
        {
            var method = "http://BaseUrlOfWebsite#2/api/applications";
            using (MultipartFormDataContent form = new MultipartFormDataContent())
            {
                //make POST API call into website # 2
                var response = await client.PostAsync(method, form);
                Log("milestone3");
            }
       }
   }
   Log("milestone4");
}

Overall call is like this:

Client code -> install API on website # 1 -> applications API on website # 2

My expected log statements are in below order:

milestone1
milestone2
milestone3
milestone4
milestone5
milestone6

Actual log order that I see in log files is as below:

milestone1
milestone2
milestone5
milestone6
milestone3
milestone4

Code doesn't causes error anywhere but it returns 500 internal server error code to calling client. I've two questions here:

  1. Why the logs are not as per my expectation? My understanding is that Register method should get blocked while calling external website as I'm using await while waiting for response.
  2. Can this out of order code execution cause 500 internal server error even though my code isn't throwing any error?

Note: Same logs get printed in expected order when I debug the install API with debugger attached in Visual Studio.

Update: If I make the call to external website client.PostAsync run synchronously forcefully using Task.Run then I get 200 OK response. Why web API results in error if I can fire and forget the call to external website and my remaining code can run synchronously to finish the web API call.

RBT
  • 24,161
  • 21
  • 159
  • 240
  • 1
    In the method MyClass.Register(); nothing is blocking it's execution so it simply continues. (I would await the Register method in the Install method then the register method would return a Task, instead of void) – IamK Sep 01 '19 at 11:23
  • 1
    The code is continuing past the async method : var response = await client.PostAsync(method, form); So 5 & 6 are displayed and then when PostAsync completes 3 & 4 are displayed. – jdweng Sep 01 '19 at 11:29
  • You should use `async` all the way down. The Register Method is not awaited anywhere. if you have awaited the on `await MyClass.Register();` in the Install method it would print milestone 2, milestone 3, 4 and 5 – Eldho Sep 01 '19 at 11:58
  • @jdweng even if the code is running synchronously then what could cause internal server error? Let's say the `install` method takes a fire and forget approach for `MyClass.Register` method. So `Install` method and the web api method `InstallProduct` should still return gracefully. Isn't it? – RBT Sep 02 '19 at 11:34
  • The response is not finished when method returns after milestone6. So any code afterwards will not have a completed response which is giving the error. – jdweng Sep 02 '19 at 11:46
  • So we're saying that ASP .NET run-time can somehow track that few threads (most likely thread-pool threads as we're using async) that a web API had spawned have still not run to completion even though the web API method (`InstallProduct`) has exited. If such a condition is traced then ASP .NET run-time considers it an error condition and hence returns 500 HTTP error code in response. So completion of web method is not a guarantee of 200 OK response. Is that a correct mental model that I'm trying to infer from this? – RBT Sep 02 '19 at 12:02

2 Answers2

3

Register method is async, it returns void, instead of Task, which could be awaited. And instead of awaiting for task your code is running synchronously.

    [Route("install")]
        public async Task<IHttpActionResult> InstallProduct()
        {
            Log("milestone1");
            await Install();
            Log("milestone6");
        }

        private async Task Install()
        {
            await MyClass.Register();

            Log("milestone5");
        }

        private static async Task Register()
        {
            Log("milestone2");
            using (var handler = new HttpClientHandler())
            {
                using (var client = new HttpClient(handler))
                {
                    var method = "http://BaseUrlOfWebsite#2/api/applications";
                    using (MultipartFormDataContent form = new MultipartFormDataContent())
                    {
                        //make POST API call into website # 2
                        var response = await client.PostAsync(method, form);
                        Log("milestone3");
                    }
                }
            }
            Log("milestone4");
        }
mayakwd
  • 530
  • 3
  • 7
  • Just to be clear, his code *was* running asynchronously (as shown by the output he was getting), he just wasn't awaiting it as he wanted. – Gabriel Luci Sep 01 '19 at 14:58
  • This is the correct way to await asynchronous code, although it still doesn't explain the 500 response. If the 500 response continues after updating the code, then look at the body of the response for the actual error message. – Gabriel Luci Sep 01 '19 at 15:00
  • @GabrielLuci the 500 response code goes away if I make `client.PostAsync` call synchronous forcefully using `Task.Run` method as per [this](https://stackoverflow.com/a/30095277/465053) post. So that means this error goes away if I wait for the call to external website to finish. The thing I'm wondering is that why it results in error if I fire and forget the call to external website? – RBT Sep 02 '19 at 11:48
  • Interesting. So what is the body of the 500 response? There should be an error message. – Gabriel Luci Sep 02 '19 at 12:24
1

In general, when you see async void in your code it’s bad news, because:

you can’t wait for its completion (as mentioned in this post already) any unhandled exceptions will terminate your process (ouch!)

Suppose you have a timer event that fires every once in a while and you want to do some asynchronous processing inside the handler, so you proceed to write something along the lines of:

var timer = new Timer();
timer.Elapse += OnTimerFired;
timer.Interval = 1000;
timer.Start();

...

private static async void Register()
{
   await Task.Delay(1000);

   // this is going to terminate your process!
   throw new Exception();
}
Moinul Islam
  • 469
  • 2
  • 9