Cédric
Cédric

Reputation: 180

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=>....);
}

Upvotes: 0

Views: 2877

Answers (2)

Ben Voigt
Ben Voigt

Reputation: 283773

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.

Upvotes: 4

J.N.
J.N.

Reputation: 536

I don't have any experience with using Task.WhenAll, but you should be able to simply await for the task collection to be completed. Like this:

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

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

I'm currently not able to try it out myself, unfortunately.

Upvotes: -1

Related Questions