如何一次使用 Task.WhenAll 到 运行 2 次计算

How to use Task.WhenAll to run 2 calculations at once

我正在努力学习如何使用异步任务,但我真的很努力只是为了让一个简单的例子起作用。我在下面提供了我要实现的目标的非常简化的版本。

public void Calculate()
{
    int a1 = 1;
    int a2 = 2;
    int b1 = 3;
    int b2 = 4;
    int c3 = 5;
    int c4 = 6;
    int c5 = 7;
    int c6 = 8;
    int c7 = 9;

    // Do next 2 lines in parallel
    int rank1 = Evaluate(a1, a2, c3, c4, c5, c6, c7);
    int rank2 = Evaluate(b1, b2, c3, c4, c5, c6, c7);

    // wait for above 2 lines and use values to get result
    int result = EvaluateOutcome(rank1, rank2);
}

public static int Evaluate(int i1, int i2, int i3, int i4, int i5, int i6, int i7)
{
    // do something complicated and return value - simulate here with random number
    Random rand = new Random();
    return rand.Next(0,10);
}

public static int EvaluateOutcome(int rank1, int rank2)
{
    return rank1 * rank2;
}

计算rank1rank2在我的真实代码中是一个漫长的过程,但计算是相互独立的。我想尝试 运行 一次计算这两个计算,希望处理时间减半。在计算结果之前,我需要等待两个计算完成。

我想我应该可以做类似的事情:

private async Task Evaluate(int a1, int a2, int b1, int b2, int c3, int c4, int c5, int c6, int c7)
{
    Task task1 = Evaluate(a1, a2, c3, c4, c5, c6, c7);
    Task task2 = Evaluate(b1, b2, c3, c4, c5, c6, c7);

    await Task.WhenAll(task1, task2);
}

但这无法编译,我不确定如何从任务中获取结果并将它们放入我的 rank1rank2 int 变量中。我认为这应该非常简单,但我找不到一个明确的例子来遵循。一些帮助将不胜感激。

您可以使用Task.Run method to offload each calculation to a ThreadPool thread, and the Task.WaitAll方法来阻塞当前线程,直到两个计算完成:

Task<int> task1 = Task.Run(() => Evaluate(a1, a2, c3, c4, c5, c6, c7));
Task<int> task2 = Task.Run(() => Evaluate(b1, b2, c3, c4, c5, c6, c7));

Task.WaitAll(task1, task2);

int rank1 = task1.Result;
int rank2 = task2.Result;

更有效的替代方法是使用 Parallel.Invoke 方法。核心区别在于当前线程也将通过执行两个计算之一参与工作。所以只会使用一个额外的线程(来自 ThreadPool):

int rank1 = default;
int rank2 = default;

var parallelOptions = new ParallelOptions()
{
    MaxDegreeOfParallelism = Environment.ProcessorCount
};

Parallel.Invoke(parallelOptions,
    () => rank1 = Evaluate(a1, a2, c3, c4, c5, c6, c7),
    () => rank2 = Evaluate(b1, b2, c3, c4, c5, c6, c7)
);

Parallel.Invoke 的文档有点令人困惑,声明它 执行每个提供的操作,可能 并行。不用担心。计算不会并行发生的唯一情况是 ThreadPool 饱和。在这种情况下,第一种方法 (Task.Run+Task.WaitAll) 也无法并行计算,同时对池的饱和度贡献更大。如果你想把 ThreadPool 从等式中去掉,你可以传递一个自定义的 TaskScheduler with the ParallelOptions, that executes the tasks on a dedicated thread per task, like the one found here. Or use the TaskCreationOptions.LongRunning 标志。