0

I have this c# MVC code and it works fine with the GetAsync and GetPost methods, but when using GetStringAsync, it freezes up at the line:

version = await client.GetStringAsync("/API/Version");

Driver code:

Task<string>[] tasks = new Task<string>[count];
for (int i = 0; i < count; i++)
{
    tasks[i] = MyHttpClient.GetVersion(port, method);
}
Task.WaitAll(tasks);
string[] results = new string[tasks.Length];
for(int i=0; i<tasks.Length; i++)
{
    Task<string> t = (Task<string>)(tasks[i]);
    results[i] = (string)t.Result;
}

HttpCilent code:

public static async Task<string> GetVersion(int port, string method)
{
    try
    {
        var client = new HttpClient();
        client.BaseAddress = new Uri("http://localhost:" + port);
        string version = null;
        if (method.ToUpper().Equals("GETSTR"))
        {
            version = await client.GetStringAsync("/API/Version");
        }
        else if (method.ToUpper().Equals("GET"))
        {
            HttpResponseMessage res = client.GetAsync("/API/Version").Result;
            version = res.Content.ReadAsStringAsync().Result;
        }
        else if (method.ToUpper().Equals("POST"))
        {
            var content = new FormUrlEncodedContent(new[] 
            {
                new KeyValuePair<string, string>("name", "jax")
            });
            HttpResponseMessage res = client.PostAsync("/API/Version", content).Result;
            version = res.Content.ReadAsStringAsync().Result;
        }
        client.Dispose();
        return version;
    }
    catch (Exception ex)
    {
        return "Error: " + ex.Message;
    }
}
}

Any ideas why?

John Saunders
  • 160,644
  • 26
  • 247
  • 397
max
  • 9,708
  • 15
  • 89
  • 144
  • 3
    You're running into a [common deadlock problem](http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html) that I describe on my blog. To fix it, replace `Task.WaitAll` with `Task.WhenAll` and `await` the returned task. – Stephen Cleary Sep 27 '14 at 00:25
  • @StephenCleary I looked a bit for a duplicate because I know I've seen this same problem 20 times before on SO. Is there a canonical "async/await code is freezing on me" question that these questions can be marked as duplicate of? – Timothy Shields Sep 27 '14 at 00:26
  • @TimothyShields: The problem is that there's many variations of this question that are all caused by the same issue (so the answer is duplicate, but not really the question). I've marked duplicates such as to [this answer](http://stackoverflow.com/questions/10343632/httpclient-getasync-never-returns-when-using-await-async/10351400#10351400) but often the OP doesn't see it as a duplicate. – Stephen Cleary Sep 27 '14 at 00:53

1 Answers1

7

Generally, you shouldn't mix async/await with .Result and .Wait(). First the fix to the GetVersion method:

public static async Task<string> GetVersion(int port, string method)
{
    try
    {
        using (var client = new HttpClient())
        {
            client.BaseAddress = new Uri("http://localhost:" + port);
            if (method.ToUpper().Equals("GETSTR"))
            {
                return await client.GetStringAsync("/API/Version")
                    .ConfigureAwait(false);
            }
            else if (method.ToUpper().Equals("GET"))
            {
                var res = await client.GetAsync("/API/Version").ConfigureAwait(false);
                return await res.Content.ReadAsStringAsync().ConfigureAwait(false);
            }
            else if (method.ToUpper().Equals("POST"))
            {
                var content = new FormUrlEncodedContent(new[] 
                {
                    new KeyValuePair<string, string>("name", "jax")
                });
                var res = await client.PostAsync("/API/Version", content)
                    .ConfigureAwait(false);
                return await res.Content.ReadAsStringAsync().ConfigureAwait(false);
            }
        }
    }
    catch (Exception ex)
    {
        return "Error: " + ex.Message;
    }
}

All I've done here is replace the <task>.Result calls with await <task>. For each await, the portion of the method following the await will be registered as a continuation of the Task being awaited -- if and when each await expression is encountered -- instead of blocking the thread in the case of .Result.

Now in your calling code you need to do the same thing:

var tasks = new Task<string>[count];
for (int i = 0; i < count; i++)
{
    tasks[i] = MyHttpClient.GetVersion(port, method);
}
var results = new string[tasks.Length];
for (int i = 0; i < tasks.Length; i++)
{
    results[i] = await tasks[i];
}

Or more simply:

var tasks = new Task<string>[count];
for (int i = 0; i < count; i++)
{
    tasks[i] = MyHttpClient.GetVersion(port, method);
}
var results = await Task.WhenAll(tasks);
Timothy Shields
  • 75,459
  • 18
  • 120
  • 173
  • Thanks for a clear answer. My caller method is synchronous so I used Task.WhenAll without await to synchronize the task and then looped through the results. – max Sep 29 '14 at 04:38
  • I actually also needed to add .ConfigureAwait(false). – max Sep 29 '14 at 17:10
  • @max Oh right I completely forgot to mention that. In fact, the `GetVersion` method should really be using that for all the `await` usage. (I'll edit it.) – Timothy Shields Sep 29 '14 at 18:08