Cédric Cédric - 1 year ago 69
C# Question

Task.WhenAll not resolving in synchronous method

I have a synchronous method that needs to call multiple times the same http end point with different parameters, wait for all requests to return and process the combined results in a synchronous fashion. Here is the simplified implementation of the synchronous method:

public void Test(IEnumerable<int> configs){
var ratings = Task.WhenAll(configs.Select(x=>Rate(x)));

ratings.Wait();

// More work done once all Rate tasks are complete
var test = ratings.Result.Select(x=>....);
}


And the simplified implementation of the Rate method is as follows:

private async Task<JToken> Rate(int arg){

var content = CreateContent(arg);

var uri = "http://fake.com/foo/bar";
var requestUri = new Uri(uri);

using (HttpClient client = new HttpClient())
{
using (HttpResponseMessage response = await client.PostAsync(requestUri, content))
{
return await response.Content.ReadAsAsync<JToken>();
}
}
}


I am able to verify that the http requests are getting posted asynchronously the way I want, however, I never get past
ratings.Wait()
. The status of
ratings
is always WaitingForActivation with
Result
showing Not yet computed. I am unsure about what is happening here.

Is wrapping the
client
and the
response
in
using
statements with
await
causing them to be disposed of before the method can return? If I call
.Result
in the
Rate
method, I get past the
ratings.Wait()
, but then http calls are made synchronously, which defeats the whole purpose.

EDIT:

As suggested below, I used Task.WaitAll, unfortunately with the same result. It looks like the individual
Rate
tasks are not resolving although I can see that I am receiving a 200 Http Code as a response in fiddler.

public void Test(IEnumerable<int> configs){
var tasks = configs.Select(x => Rate(x)).ToArray();

Task.WaitAll(tasks);

var ratings = tasks.Select(x => x.Result);

// More work done once all Rate tasks are complete
var test = ratings.Result.Select(x=>....);
}

Answer Source

You have a deadlock. The PostAsync completion callbacks are sent to your thread's event queue, which will get processed only after your current function returns to the main event loop. Since you're not returning until the completion does its thing... deadlock.

You can avoid this by starting the Tasks on the thread pool. You can request completion callbacks on the thread pool via GetAwaiter().ConfigureAwait(false), but this has to be done on each Task, not just the composite Task, or the Task returned by an async function. Better to launch the whole async function on the thread pool using Task.Run so that its subtasks automatically have affinity to the threadpool and not your UI thread.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download