尝试与 .WhenAll 并行运行多个任务,但任务并未并行运行

问题描述 投票:0回答:1

我有一种方法可以将 csv 文件转换为特定模型,因为有 700k 多条记录,我想将其拆分为多个任务。我在方法中使用

.Skip
.Take
,因此该方法的每次运行都知道从哪里开始以及需要多少个。我有一个数字 1-10 的列表,我想对其进行迭代并创建任务来运行此方法,使用该迭代器来创建任务并进行一些数学计算以确定要跳过的记录数。

这是我创建任务的方式:

var numberOfTasksList = Enumerable.Range(1, 10).ToList();
//I left out the math to determine rowsPerTask used as a parameter in the below method for brevity's sake
var tasks = numberOfTasksList.Select(i
                =>  ReadRowsList<T>(props, fields, csv, context, zohoEntities, i, i*rowsPerTask, (i-1)*rowsPerTask));

           await Task.WhenAll(tasks);

使用的

ReadRowsList
方法如下所示(不带参数):

public static async Task<string> ReadRowsList<T>(...parameters) where T : class, new()
   {
     //work to run
     return $"added rows for task {i}";
   }

该方法返回的字符串只是一个简单的行,上面写着 $“为任务 {i} 添加了行”,因此它实际上并不是一个正确的异步/等待,因为我只是返回一个字符串来表示迭代何时完成。

但是,当我运行程序时,该方法会等待第一次迭代(其中 i=1)完成,然后再开始运行程序的第二次迭代,因此它不是并行运行。在异步/并行编程方面,我不是最好的,但是是否有明显的情况会导致任务必须等到上一个迭代完成才能开始下一个任务?根据我的理解,使用上面的代码创建任务并使用

.WhenAll(tasks)
将为每次迭代创建一个新线程,但我一定错过了一些东西。

c# async-await parallel-processing
1个回答
7
投票

简而言之:

  1. async
    不等于多线程;和
  2. 创建一个函数
    async Task
    不会使其异步

Task.WhenAll
使用没有
await
的假装异步代码运行时,当前线程无法“放开”手头的任务,也无法开始处理其他任务。

正如评论中指出的那样,构建链会通过以下方式警告您:

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.  

简单的例子

让我们考虑两个具有相同签名的函数,一个具有异步代码,另一个不具有。

static async Task DoWorkPretendAsync(int taskId)
{
    Console.WriteLine($"Thread: {Thread.CurrentThread.ManagedThreadId} -> task:{taskId} > start");
    Thread.Sleep(TimeSpan.FromSeconds(1));
    Console.WriteLine($"Thread: {Thread.CurrentThread.ManagedThreadId} -> task:{taskId} > done");
}

static async Task DoWorkAsync(int taskId)
{
    Console.WriteLine($"Thread: {Thread.CurrentThread.ManagedThreadId} -> task:{taskId} > start");
    await Task.Delay(TimeSpan.FromSeconds(1));
    Console.WriteLine($"Thread: {Thread.CurrentThread.ManagedThreadId} -> task:{taskId} > done");
}

让我们用以下代码片段来测试它们:

await DoItAsync(DoWorkPretendAsync);
Console.WriteLine();
await DoItAsync(DoWorkAsync);

async Task DoItAsync(Func<int, Task> f)
{
    var tasks = Enumerable.Range(start: 0, count: 3).Select(i => f(i));
    Console.WriteLine("Before WhenAll");
    await Task.WhenAll(tasks);
    Console.WriteLine("After WhenAll");
}

W 可以看到使用

DoWorkPretendAsync
任务是按顺序执行的。

Before WhenAll
Thread: 1 -> task:0 > start
Thread: 1 -> task:0 > done
Thread: 1 -> task:1 > start
Thread: 1 -> task:1 > done
Thread: 1 -> task:2 > start
Thread: 1 -> task:2 > done
After WhenAll

Before WhenAll
Thread: 1 -> task:0 > start
Thread: 1 -> task:1 > start
Thread: 1 -> task:2 > start
Thread: 5 -> task:0 > done
Thread: 5 -> task:2 > done
Thread: 7 -> task:1 > done
After WhenAll

注意事项:

  • 即使使用真正的异步,所有任务也由同一线程启动;
  • 在此特定运行中,其中两个任务由同一线程(id:5)完成。这根本无法保证 - 任务可以在一个线程上启动,然后池中的另一个线程可以稍后拾取它。
© www.soinside.com 2019 - 2024. All rights reserved.