-1

I'm trying to make a call to another API using POST methods through Asynctask. Everything works fine (the request is correctly executed because it launches my SQL request) but I can't get any response from the external server.

I can see that I don't need to run the task to make it execute but I don't have any result.

    [HttpPost]
    public string Post([FromBody]JObject value)
    {
        if (MesureController.CheckJsonIntegrity<Mesure>(value))
        {
            var task = MesureAsync(value);
            return task.Result;
        }
        return null;
    }

    static async Task<string> MesureAsync(JObject value)
    {
        using (client)
        {
            string url = ConfigurationManager.AppSettings["internalserver:url"];
            var json_string = new Dictionary<string, string>
            {
                {"json_string", value.ToString() }
            };
            var content = new FormUrlEncodedContent(json_string);
            var response = await client.PostAsync(url + "Mesure/Index", content);
            string resultContent = await response.Content.ReadAsStringAsync();
            return resultContent;
        }
    }
Orionss
  • 725
  • 2
  • 10
  • 26
  • 7
    Generally, once you apply `async` somewhere, you'll find yourself converting the calling methods to be `async` as well, all the way up. Calling `Result` can easily result in a deadlock, which I think is what you're trying to describe here. – Damien_The_Unbeliever Aug 02 '17 at 09:07
  • I can't convert my Post method in async because I would mean that I should return a Task again, and I need to return an object (in this case a string), it's an API. Can't we call an async task in something else than a task ? – Orionss Aug 02 '17 at 09:17
  • 2
    Yes, you would convert your method to return a `Task`. What's the problem with that? – Damien_The_Unbeliever Aug 02 '17 at 09:18
  • 1
    ASP.NET will automatically "unwrap" `Task` and create response with string value – Fabio Aug 02 '17 at 09:20
  • 1
    Why don't you just make `Post` `async`? – Liam Aug 02 '17 at 09:23
  • Thanks, it worked. Can you please make your comment as answer to be able to make it solved ? :) – Orionss Aug 02 '17 at 09:24

3 Answers3

1

You're seeing a common deadlock that I describe in more detail on my blog. In summary, ASP.NET (full, not Core) code runs within a "request context" that only allows one thread to work on a request at a time. When MesureAsync sends the POST to the other API, it returns an incomplete task. Your Post method then blocks the current thread, waiting for that task. Later, when the POST to the other API completes, MeasureAsync attempts to resume executing within that same request context, but it can't because Post has blocked a thread within that request context, and the request context only allows one thread at a time.

So, you end up with Post taking up the request context waiting for MeasureAsync to complete, and MeasureAsync waiting for Post to give up the request context so that it can complete. Classic deadlock.

The best solution is to go "async all the way", i.e., don't block on async code. In this case, replace Result with await:

[HttpPost]
public string Post([FromBody]JObject value)
{
  if (MesureController.CheckJsonIntegrity<Mesure>(value))
  {
    return await MesureAsync(value);
  }
  return null;
}

If you try to compile this now, it will give you a compiler error that tells you exactly what to do with Post to get it to work:

[HttpPost]
public async Task<string> Post([FromBody]JObject value)
{
  if (MesureController.CheckJsonIntegrity<Mesure>(value))
  {
    return await MesureAsync(value);
  }
  return null;
}

...and you're done!

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
0

No result was return in the original code because of the mixing of blocking and async code.

The actions syntax should be updated to be async all the way and also to allow better content negotiation.

Assuming this code is for Asp.Net-Core, the actions would be updated to

[HttpPost]
public async Task<IActionResult> Post([FromBody]JObject value) {
    if (MesureController.CheckJsonIntegrity<Mesure>(value)) {
        var measure = await MesureAsync(value);
        return Ok(measure);
    }
    return BadRequest();
}

If using WebAPI 2.* then change IActionResult to IHttpActionResult and it will work the same way.

Nkosi
  • 235,767
  • 35
  • 427
  • 472
-1

You have to ensure Task will run.

Use this snippet:

Task.Run(()=>MesureAsync(value)).Result;

Ygalbel
  • 5,214
  • 1
  • 24
  • 32