Параллель.ForEach с HttpClient и ContinueWith
У меня есть метод, который пытается загрузить данные из нескольких 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;
}
В моем модульном тесте (я заглушаю _httpClient, чтобы вернуть известный набор текста) я в основном получаю
Последовательность не содержит элементов
Это происходит потому, что метод возвращается до завершения задач.
Если я добавлю .Подождите () на конце моего .ContinueWith () вызывает, он проходит, но Я уверен, что я злоупотребляю API здесь...
1 ответ:
Если вы хотите блокирующий вызов, который загружается параллельно с помощью 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; }
Это создает массив задач, по одной для каждого Url, который представляет собой операцию GetAsync / Deserialize. Это предполагает, что Url-адрес возвращает массив JSON TContent. Пустой массив или массив с одним членом будет десериализован нормально, но не один объект без массива.