这是几周来一直运行良好的原始代码。在我刚刚做的测试中,它在100次尝试中失败了。
using (var httpClient = new HttpClient())
{
var tasks = new List<Task>();
tasks.Add(httpClient.GetAsync(new Uri("..."))
.ContinueWith(request =>
{
request.Result.Content.ReadAsAsync<IEnumerable<Foo>>()
.ContinueWith(response =>
{
foos = response.Result;
});
}));
tasks.Add(httpClient.GetAsync(new Uri("..."))
.ContinueWith(request =>
{
request.Result.Content.ReadAsAsync<Bar>()
.ContinueWith(response =>
{
bar = response.Result;
});
}));
await Task.WhenAll(tasks);
}
此代码在100次尝试中失败了9次,其中一个或两个元组值为null
。
var APIresponses = await HttpClientHelper.GetAsync
<
IEnumerable<Foo>,
Bar
>
(
new Uri("..."),
new Uri("...")
);
foos = APIresponses.Item1;
bar = APIresponses.Item2;
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);
});
}
public static async Task<Tuple<T1, T2>> GetAsync<T1, T2>(Uri URI1, Uri URI2)
{
T1 item1 = default(T1);
T2 item2 = default(T2);
var httpClient = new HttpClient();
var tasks = new List<Task>()
{
GetAsync<T1>(httpClient, URI1, response =>
{
item1 = response.Result;
}),
GetAsync<T2>(httpClient, URI2, response =>
{
item2 = response.Result;
})
};
await Task.WhenAll(tasks);
return Tuple.Create(item1, item2);
}
将代码修改为如此,并且在100次尝试中它将再次失败。
await Task.WhenAll(tasks);
System.Diagnostics.Debug.WriteLine("tasks complete");
System.Diagnostics.Debug.WriteLine(item1);
System.Diagnostics.Debug.WriteLine(item2);
return Tuple.Create(item1, item2);
}
我已经看了半个多小时,但我没看到错误在哪里。有人看到了吗?
答案 0 :(得分:2)
此代码:
request.Result.Content.ReadAsAsync<T>()
.ContinueWith(continuationAction);
返回一个任务,但永远不会等待该任务(并且不会向其添加Continuation)。因此,在Task.WhenAll
返回之前,可能无法设置项目。
然而,原始解决方案似乎也有同样的问题。
我的猜测是你正在处理值类型,并且两者都有竞争条件,但在第二个例子中,你很早就将值类型(虽然它们仍然是它们的默认值)复制到元组中。在其他示例中,您需要等待很长时间才能复制或使用它们,以便设置值的问题延续运行。
答案 1 :(得分:2)
要解决来自other question的评论,您很少需要将async
/ await
与ContinueWith
混在一起。您可以在async
lambdas的帮助下执行“fork”逻辑,例如,问题中的代码可能如下所示:
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;
// ...
}
答案 2 :(得分:1)
编辑:不接受我自己的答案,但留待参考。代码工作,带有catch:ContinueWith loses the SynchronizationContext
感谢@jbl和@MattSmith让我走上正轨。
问题确实是Task.WhenAll
不等待继续。解决方案是设置TaskContinuationOptions.AttachedToParent
。
所以这个
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);
});
}
变成这个
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);
}
的更多信息