Параллель.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 4

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. Пустой массив или массив с одним членом будет десериализован нормально, но не один объект без массива.