This is the original code that had been running fine for a few weeks. In a test I just did, it failed 0 out of 100 attempts.
using (var httpClient = new Http
To address the comment from to your other question, you very rarely need to mix async
/await
with ContinueWith
. You can do the "fork" logic with help of async
lambdas, e.g., the code from the question may look like this:
using (var httpClient = new HttpClient())
{
Func<Task<IEnumerable<Foo>>> doTask1Async = async () =>
{
var request = await httpClient.GetAsync(new Uri("..."));
return response.Content.ReadAsAsync<IEnumerable<Foo>>();
};
Func<Task<IEnumerable<Bar>>> doTask2Async = async () =>
{
var request = await httpClient.GetAsync(new Uri("..."));
return response.Content.ReadAsAsync<IEnumerable<Bar>>();
};
var task1 = doTask1Async();
var task2 = doTask2Async();
await Task.WhenAll(task1, task2);
var result1 = task1.Result;
var result2 = task2.Result;
// ...
}
Edit: unaccepting my own answer, but leaving it for reference. The code works, with a catch: ContinueWith loses the SynchronizationContext
Thanks to @jbl and @MattSmith for putting me on the right track.
The problem was indeed that Task.WhenAll
does not wait on the continuations. The solution is to set TaskContinuationOptions.AttachedToParent
.
So this
private static Task GetAsync<T>(HttpClient httpClient, Uri URI, Action<Task<T>> continuationAction)
{
return httpClient.GetAsync(URI)
.ContinueWith(request =>
{
request.Result.EnsureSuccessStatusCode();
request.Result.Content.ReadAsAsync<T>()
.ContinueWith(continuationAction);
});
}
becomes this
private static Task GetAsync<T>(HttpClient httpClient, Uri URI, Action<Task<T>> continuationAction)
{
return httpClient.GetAsync(URI)
.ContinueWith(request =>
{
request.Result.EnsureSuccessStatusCode();
request.Result.Content.ReadAsAsync<T>()
.ContinueWith(continuationAction, TaskContinuationOptions.AttachedToParent);
}, TaskContinuationOptions.AttachedToParent);
}
More info available on MSDN: Nested Tasks and Child Tasks
This code:
request.Result.Content.ReadAsAsync<T>()
.ContinueWith(continuationAction);
returns a task, but that task is never awaited (and no Continuation is added to it). So the item's might not get set before Task.WhenAll
returns.
However, the original solution seems to have the same problem.
My guess is that you are dealing with value types, and that both have a race condition, but in the 2nd example, you copy the value types early enough (while they are still their default value) into the Tuple. Where as in your other examples you wait long enough before copying them or using them such that the problem continuation that sets the values has run.