使用HttpClient和ContinueWith的Paralell.ForEach

Ale*_*lex 4 c# asynchronous task-parallel-library async-await dotnet-httpclient

我有一个方法尝试从并行的几个URL下载数据,并返回一个IEnumerable反序列化类型

该方法如下所示:

    public IEnumerable<TContent> DownloadContentFromUrls(IEnumerable<string> urls)
    {
        var list = new List<TContent>();

        Parallel.ForEach(urls, url =>
        {
            lock (list)
            {
                _httpClient.GetAsync(url).ContinueWith(request =>
                {
                    var response = request.Result;
                    //todo ensure success?

                    response.Content.ReadAsStringAsync().ContinueWith(text =>
                    {
                        var results = JObject.Parse(text.Result)
                            .ToObject<IEnumerable<TContent>>();

                        list.AddRange(results);
                    });
                });
            }
        });

        return list;
    }
Run Code Online (Sandbox Code Playgroud)

在我的单元测试中(我存根_httpClient返回一组已知的文本)我基本上得到了

序列不包含任何元素

这是因为该方法在任务完成之前返回.

如果我在.ContinueWith()调用结束时添加.Wait(),它会通过,但我确定我在这里滥用了API ...

Tre*_*ott 7

如果你想要一个使用HttpClient.GetAsync方法并行下载的阻塞调用,那么你应该像这样实现它:

public IEnumerable<TContent> DownloadContentFromUrls<TContent>(IEnumerable<string> urls)
{
    var queue = new ConcurrentQueue<TContent>();

    using (var client = new HttpClient())
    {
        Task.WaitAll(urls.Select(url =>
        {
            return client.GetAsync(url).ContinueWith(response =>
            {
                var content = JsonConvert.DeserializeObject<IEnumerable<TContent>>(response.Result.Content.ReadAsStringAsync().Result);

                foreach (var c in content)
                    queue.Enqueue(c);
            });
        }).ToArray());
    }

    return queue;
}
Run Code Online (Sandbox Code Playgroud)

这将创建一个任务数组,每个Url对应一个任务,表示GetAsync/Deserialize操作.这假设Url返回TContent的Json数组.空数组或单个成员数组将反序列化精细,但不是单个无数组对象.