首页 > 解决方案 > 如何使用 Task.WhenAll 一次运行 2 个计算

问题描述

我正在尝试学习如何使用异步任务,并且我真的很努力只是为了让一个简单的示例正常工作。我在下面提供了我想要实现的非常简化的版本。

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);
}

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

标签: c#async-awaitparallel-processingtask

解决方案


您可以使用该Task.Run方法将每个计算卸载到一个ThreadPool线程,并使用该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摆脱等式,您可以使用 传递一个自定义TaskSchedulerParallelOptions它在每个任务的专用线程上执行任务,就像在这里找到的那​​样。或者使用TaskCreationOptions.LongRunning旗帜。


推荐阅读