C# Правете асинхронни http заявки в foreach цикъл

Трябва да направя множество уеб заявки, където 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: Колекцията е променена; операцията по изброяване може да не се изпълни.

Можем ли все пак да постигнем http async извиквания в рамките на foreach?


person Vijay    schedule 22.10.2019    source източник
comment
Не бих използвал Parallel.ForEach за това. Наистина просто искате да преместите своя Task.WhenAll извън цикъла, в който добавяте задачи към списъка си със задачи.   -  person Jonathon Chase    schedule 22.10.2019
comment
Когато използвате parallel foreach, искате да сте сигурни, че използвате колекции, безопасни за нишки от пространството от имена на едновременни колекции docs.microsoft.com/en-us/dotnet/api/   -  person DetectivePikachu    schedule 22.10.2019
comment
Освен това, ако все пак решите да извършвате обажданията си едновременно, може да се наложи да увеличите времето за изчакване на всяко повикване, тъй като може да имате заявка, блокирана от максималния брой едновременни сокети, отворени в някои среди.   -  person Jonathon Chase    schedule 22.10.2019
comment
От документация: HttpClient е предназначен да бъде създаден веднъж и повторно използван през целия живот на приложението. Създаването на клас HttpClient за всяка заявка ще изчерпи броя на наличните сокети при големи натоварвания.   -  person Theodor Zoulias    schedule 22.10.2019
comment
Благодаря на всички за отговорите, гласувах за всички :)   -  person Vijay    schedule 22.10.2019


Отговори (2)


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

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

Цикълът for попълва колекцията и след това Task.WhenAll() блокира, докато заявките завършат.

person Kevin Anderson    schedule 22.10.2019

Parallel.ForEach е за CPU-интензивни операции и не е предназначен за I/O-интензивни операции или за async.

Можете да await вътре в foreach цикъл съвсем добре. Самият метод, съдържащ вашия цикъл, трябва да бъде асинхронен.

person Cory Nelson    schedule 22.10.2019