首页 > 解决方案 > 通过异步示例了解 C# 中的并行编程

问题描述

我正在尝试理解并行编程,并且我希望我的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 方法在多个线程上运行。

任何帮助,将不胜感激!

标签: c#parallel-processingtask-parallel-libraryparallel.foreach

解决方案


这里发生的情况是,您永远不会等待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期望 a Func<T, TResult>,它会将async带有 no 的 lambda解释returnasync Task方法而不是async void,从而为我们提供一些我们可以显式await


推荐阅读