C # Сделать асинхронные HTTP-запросы в цикле foreach - PullRequest
0 голосов
/ 22 октября 2019

Мне нужно сделать несколько веб-запросов, где URI находятся в DataTable. Ранее у меня был код ниже. Но я понял, что это делает синхронные вызовы, так как await будет ожидать завершения вызова GET / POST и обработки ответа, затем он перейдет к следующей итерации.

foreach (DataRow dr in dt.Rows)
{
    activeTasks.Add(SendRequestAsync(dr));
    Task.WhenAll(activeTasks).Wait();
}

private async Task<string> SendRequestAsync(DataRow dr)
{
    using (var client = new HttpClient())
    {
        string reqMethod = (dr["RequestMethod"] != null && dr["RequestMethod"].ToString() != "") ? dr["RequestMethod"].ToString() : "GET";
        client.BaseAddress = new Uri(dr["URL"].ToString());
        client.DefaultRequestHeaders.Accept.Clear();
        string reqContentType = (dr["RequestContentType"] != null && dr["RequestContentType"].ToString() != "") ? dr["RequestContentType"].ToString() : "text/xml";
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(reqContentType));

        HttpResponseMessage response = null;
        try
        {
            if (reqMethod == "GET")
                response = await client.GetAsync(client.BaseAddress.AbsoluteUri);
            else
                response = await client.PostAsync(client.BaseAddress.AbsoluteUri, null);

            response.EnsureSuccessStatusCode();
            var responseText = await response.Content.ReadAsStringAsync();
            return responseText;
        }
        catch (Exception e)
        {
            return "-1";
        }
    }
}

Затем я наткнулся на функцию Parallel и использовал взамен Parallel.ForEach . Например:

Parallel.ForEach(rows, dr =>
{
    activeTasks.Add(SendRequestAsync(dr));
    Task.WhenAll(activeTasks).Wait();
});

Это работает нормально, параллелизм достигнут, запросы асинхронны и выполняются за доли времени по сравнению с более ранним решением. Но проблема в том, что это ненадежно - иногда я получаю сообщения об ошибках типа

  • System.IndexOutOfRangeException: индекс находился за пределами массива
  • System.InvalidOperationException: Collection был изменен;Операция перечисления может не выполняться.

Можно ли в любом случае выполнить http-асинхронные вызовы внутри foreach?

Ответы [ 2 ]

5 голосов
/ 22 октября 2019

Как сказал @Johnathon_Chase, просто переместите ваш вызов WhenAll() за пределы цикла:

foreach (DataRow dr in dt.Rows)
{
    activeTasks.Add(SendRequestAsync(dr));
}
Task.WhenAll(activeTasks).Wait();

Цикл for заполняет коллекцию, а затем Task.WhenAll() блокируется, когда запросы завершаются.

4 голосов
/ 22 октября 2019

Parallel.ForEach предназначен для операций с интенсивным использованием процессора и не предназначен для операций с интенсивным вводом-выводом или асинхронных операций.

Вы можете await внутри цикла foreach просто отлично. Однако метод, содержащий ваш цикл, должен быть сам асинхронным.

...