如何在 C# 中 运行 并行和顺序方法?

How do I run a method both parallel and sequentially in C#?

我有一个 C# 控制台应用程序。在这个应用程序中,我有一个方法,我将调用 DoWorkAsync。对于这个问题的上下文,这个方法看起来像这样:

private async Task<string> DoWorkAsync()
{
  System.Threading.Thread.Sleep(5000);

  var random = new Random();

  var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
  var length = random.Next(10, 101);

  await Task.CompletedTask;
  return new string(Enumerable.Repeat(chars, length)
      .Select(s => s[random.Next(s.Length)]).ToArray());
}

我从另一种方法调用 DoWorkAsync,该方法确定 a) 这将获得多少次 运行 和 b) 每次调用是并行还是顺序 运行。该方法如下所示:

private async Task<Task<string>[]> DoWork(int iterations, bool runInParallel)
{
  var tasks = new List<Task<string>>();
  for (var i=0; i<iterations; i++)
  {
    if (runInParallel)
    {
      var task = Task.Run(() => DoWorkAsync());
      tasks.Add(task);
    }
    else
    {
      await DoWorkAsync();
    }
  }

  return tasks.ToArray();
}

所有任务完成后,我想显示结果。为此,我的代码如下所示:

var random = new Random();
var tasks = await DoWork(random.Next(10, 101);
Task.WaitAll(tasks);

foreach (var task in tasks)
{
  Console.WriteLine(task.Result);
}

如果代码 运行s 并行(即 runInParalleltrue),此代码将按预期工作。但是,当 runInParallel 为假时(即我想按顺序 运行 任务),Task 数组不会被填充。因此,调用方没有任何结果可供使用。我不知道如何解决它。我不确定如何将方法调用添加为 Task,它将按顺序 运行。我知道任务背后的想法是 运行 并行。但是,我需要在并行和顺序之间切换。

谢谢!

我认为您最好执行以下操作:

  1. 创建一个函数来创建(冷)任务列表(或数组 Task<string>[],例如)。不需要 运行 他们。我们称之为 GetTasks()

var jobs = GetTasks();

  1. 然后,如果您想“按顺序”运行它们,只需
var results = new List<string>();
foreach (var job in jobs)
{
    var result = await job;
    results.Add(result);
}
return results;

如果你想 运行 它们并行:

foreach (var job in jobs) 
{
    job.Start();
}
await results = Task.WhenAll(jobs);

另一个注释,

这一切本身应该是 Task<string[]>Task<Task<... 闻起来像个问题。

the Task array doesn't get populated.

所以填充它:

else
{
  var task = DoWorkAsync();
  tasks.Add(task);
  await task;
}

P.S.

另外你的 DoWorkAsync 在我看来有点不对,为什么 Thread.Sleep 而不是 await Task.Delay (这是模拟异步执行的更正确的方法,你也不需要 await Task.CompletedTask 这样)。如果您希望 DoWorkAsync 被 CPU 绑定,只需将其设置为:

private Task<string> DoWorkAsync()
{
    return Task.Run(() =>
    {
        // your cpu bound work
        return "string";
    });
}

之后你可以做这样的事情(对于async/cpu绑定的工作):

private async Task<string[]> DoWork(int iterations, bool runInParallel)
{
    if(runInParallel)
    {
        var tasks = Enumerable.Range(0, iterations)
            .Select(i => DoWorkAsync());
        return await Task.WhenAll(tasks);
    }
    else
    {
        var result = new string[iterations];
        for (var i = 0; i < iterations; i++)
        {
            result[i] = await DoWorkAsync();
        }
        return result;
    }
}

为什么 DoWorkAsync 是一个 async 方法?

它目前没有做任何异步的事情。

您似乎正试图利用多线程来提高昂贵的 CPU 绑定工作的性能,因此您最好使用专为此设计的 Parallel.For目的:

private string DoWork()
{
    System.Threading.Thread.Sleep(5000);

    var random = new Random();

    var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    var length = random.Next(10, 101);
    
    return new string(Enumerable.Repeat(chars, length)
        .Select(s => s[random.Next(s.Length)]).ToArray());
}

private string[] DoWork(int iterations, bool runInParallel)
{
    var results = new string[iterations];

    if (runInParallel)
    {
        Parallel.For(0, iterations - 1, i => results[i] = DoWork());
    }
    else
    {
        for (int i = 0; i < iterations; i++) results[i] = DoWork();
    }
    
    return results;
}

然后:

var random = new Random();
var serial = DoWork(random.Next(10, 101));
var parallel = DoWork(random.Next(10, 101), true);