Понимать параллельное программирование в C # с асинхронными примерами - PullRequest
1 голос
/ 04 октября 2019

Я пытаюсь понять параллельное программирование, и я бы хотел, чтобы мои async методы выполнялись в нескольких потоках. Я написал что-то, но это не работает так, как я думал.

Код

public static async Task Main(string[] args)
{
    var listAfterParallel =  RunParallel(); // Running this function to return tasks
    await Task.WhenAll(listAfterParallel); // I want the program exceution to stop until all tasks are returned or tasks are completed
    Console.WriteLine("After Parallel Loop"); // But currently when I run program, after parallel loop command is printed first
    Console.ReadLine();
}

public static async Task<ConcurrentBag<string>> RunParallel()
{
     var client = new System.Net.Http.HttpClient();
     client.DefaultRequestHeaders.Add("Accept", "application/json");
     client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com");
     var list = new List<int>();
     var listResults = new ConcurrentBag<string>();
     for (int i = 1; i < 5; i++)
     {
       list.Add(i);
     }
     // Parallel for each branch to run await commands on multiple threads. 
     Parallel.ForEach(list, new ParallelOptions() { MaxDegreeOfParallelism = 2 }, async (index) =>
     {
         var response = await client.GetAsync("posts/" + index);
         var contents = await response.Content.ReadAsStringAsync();
         listResults.Add(contents);
         Console.WriteLine(contents);
     });
     return listResults;
}

Я бы хотел, чтобы функция RunParallel завершилась до того, как будет напечатано «После параллельного цикла». Также я хочу, чтобы метод get posts работал в нескольких потоках.

Любая помощь будет признательна!

Ответы [ 2 ]

1 голос
/ 04 октября 2019

Здесь происходит то, что вы никогда не ждете завершения блока Parallel.ForEach - вы просто возвращаете сумку, в которую он в конечном итоге закачается. Причина этого в том, что, поскольку Parallel.ForEach ожидает Action делегатов, вы создали лямбду, которая возвращает void, а не Task. Хотя методы async void являются действительными, они, как правило, продолжают свою работу в новом потоке и возвращаются к вызывающей стороне, как только они await Задача, и поэтому метод Parallel.ForEach считает, что обработчик завершен, несмотря на то, чтооставшаяся работа переходит в отдельный поток.

Вместо этого используйте здесь синхронный метод;

Parallel.ForEach(list, new ParallelOptions() { MaxDegreeOfParallelism = 2 }, index => 
{
    var response = client.GetAsync("posts/" + index).Result;

    var contents = response.Content.ReadAsStringAsync().Result;
    listResults.Add(contents);
    Console.WriteLine(contents);
});

Если вам абсолютно необходимо использовать await внутри, оберните его в Task.Run(...).GetAwaiter().GetResult();

Parallel.ForEach(list, new ParallelOptions() { MaxDegreeOfParallelism = 2 }, index => Task.Run(async () =>
{
    var response = await client.GetAsync("posts/" + index);

    var contents = await response.Content.ReadAsStringAsync();
    listResults.Add(contents);
    Console.WriteLine(contents);
}).GetAwaiter().GetResult();

В этом случае, однако, Task.run обычно переходит в новый поток, поэтому мы подорвали большую часть контроля Parallel.ForEach;лучше использовать async до конца;

var tasks = list.Select(async (index) => {
        var response = await client.GetAsync("posts/" + index);

        var contents = await response.Content.ReadAsStringAsync();
        listResults.Add(contents);
        Console.WriteLine(contents);
    });
await Task.WhenAll(tasks);

Поскольку Select ожидает Func<T, TResult>, он будет интерпретировать async лямбда без returnкак async Task метод вместо async void, и, таким образом, дать нам то, что мы можем явно await

0 голосов
/ 05 октября 2019

Взгляните на это: Нет потока

Когда вы выполняете несколько одновременных веб-запросов, тяжелый труд выполняет не ваш ЦП. Это процессор веб-сервера, который обслуживает ваши запросы. Ваш процессор ничего не делает в течение этого времени. Это не в особом состоянии ожидания или что-то в этом роде. Аппаратное обеспечение внутри вашей коробки, которое работает, это ваша сетевая карта, которая записывает данные в вашу оперативную память. Когда ответ получен, ваш ЦП будет уведомлен о поступивших данных, чтобы он мог что-то с ними сделать.

Вам нужен параллелизм, когда вы выполняете тяжелую работу внутри коробки, а не когда вы хотите тяжелую работу. работа, которая должна быть сделана внешним миром. С точки зрения вашего процессора, даже ваш жесткий диск является частью внешнего мира. Таким образом, все, что относится к веб-запросам, относится и к запросам, направленным на файловые системы и базы данных. Эти рабочие нагрузки называются привязанными к вводу / выводу , чтобы их можно было отличить от так называемых связанных с CPU рабочих нагрузок.

Для рабочих нагрузок, связанных с I / O, инструмент, предлагаемый платформой .NET, является асинхронным Task. В библиотеках есть несколько API, которые возвращают Task объекты. Для достижения параллелизма вы обычно запускаете несколько задач, а затем await их с Task.WhenAll. Существуют также более продвинутые инструменты, такие как Библиотека потока данных TPL , построенная поверх Tasks. Он предлагает такие возможности, как буферизация, пакетная обработка, настройка максимальной степени параллелизма и многое другое.

...