Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Deadlock using async & await

I want my program to follow this callstack/workflow:

  1. dispatch()
  2. authorize()
  3. httpPost()

My idea was that httpPost() will be async, while the other 2 methods remain non-async. However, for some reason, it would not work unless I made 2 and 3 async. Maybe I still have some misunderstandings.

To my understanding I can either:

  1. use the await keyword when calling the async method (this will suspend the method and continue after the async method completes), or
  2. omit the await keyword and instead call Task.Result of the async methods return value, which will block until the result is available.

Here is the working example:

private int dispatch(string options)
{
    int res = authorize(options).Result;
    return res;
}

static async private Task<int> authorize(string options)
{
    string values = getValuesFromOptions(options);
    KeyValuePair<int, string> response = await httpPost(url, values);
    return 0;
}

public static async Task<KeyValuePair<int, string>> httpPost(string url, List<KeyValuePair<string, string>> parameters)
{
    var httpClient = new HttpClient(new HttpClientHandler());
    HttpResponseMessage response = await httpClient.PostAsync(url, new FormUrlEncodedContent(parameters));
    int code = (int)response.StatusCode;
    response.EnsureSuccessStatusCode();

    string responseString = await response.Content.ReadAsStringAsync();
    return new KeyValuePair<int, string>(code, responseString);
}

Here is the non-working example:

private int dispatch(string options)
{
    int res = authorize(options).Result;
    return res;
}

static private int authorize(string options)
{
    string values = getValuesFromOptions(options);
    Task<KeyValuePair<int, string>> response = httpPost(url, values);
    doSomethingWith(response.Result);    // execution will hang here forever
    return 0;
}

public static async Task<KeyValuePair<int, string>> httpPost(string url, List<KeyValuePair<string, string>> parameters)
{
    var httpClient = new HttpClient(new HttpClientHandler());
    HttpResponseMessage response = await httpClient.PostAsync(url, new FormUrlEncodedContent(parameters));
    int code = (int)response.StatusCode;
    response.EnsureSuccessStatusCode();

    string responseString = await response.Content.ReadAsStringAsync();
    return new KeyValuePair<int, string>(code, responseString);
}

I also tried to have all 3 methods non-async, replacing the awaits in httpPost with .Results, but then it hangs forever on the line HttpResponseMessage response = httpClient.PostAsync(url, new FormUrlEncodedContent(parameters)).Result;

Could someone enlighten me and explain what my mistake is?

like image 200
user826955 Avatar asked Dec 16 '22 06:12

user826955


1 Answers

You have a SynchronizationContext, and that context is being captured when you await so that the continuation(s) can run in that context.

You're starting an async task, scheduling a continutation to run in your main context at some later point.

Then, before the async operation is done, you have code in your main context doing a blocking wait on the async operation. The continuation cannot be scheduled to run because the context is busy waiting on the continuation. Classic deadlock.

This is why it's important to "async all the way up", as you did in your first example.

There are a few hacks that can work around the deadlock in the second example, but it's still not something you should be doing. The entire point of going asynchronous is to avoid blocking your thread(s). If you just go doing a blocking wait on the task anyway you're defeating the purpose of going asynchronous. Either make everything asynchronous, or nothing asynchronous, unless you don't have a choice.

like image 127
Servy Avatar answered Dec 18 '22 10:12

Servy