【问题标题】:How to use Task.WhenAll to run 2 calculations at once如何使用 Task.WhenAll 一次运行 2 个计算
【发布时间】:2021-08-25 14:23:50
【问题描述】:

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

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() ...添加static Task<int> EvaluateAsync(int i1, ...) => Task.Run((i1, ..). => Evaluate(i1,...);然后Task<int> task1 = EvaluateAsync(a1, ...);然后在await Task.WhenAll之后你可以得到task1.Result作为rank1类似的东西task2
  • 这听起来不错塞尔文。如果您能将完整的代码放入答案中,我将不胜感激。非常感谢
  • 或者更好:参数集列表,并将其传递给Parallel.ForEach
  • 你能告诉我如何请查理
  • @Selvin 如果您能提供建议的代码的详细信息,那就太好了。我认为这就是答案,但我自己无法弄清楚 TIA

标签: c# async-await parallel-processing task


【解决方案1】:

您可以使用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 排除在等式之外,您可以通过ParallelOptions 传递自定义TaskScheduler,它在每个任务的专用线程上执行任务,就像找到here 的线程一样。或者使用TaskCreationOptions.LongRunning 标志。

【讨论】:

  • 谢谢西奥多。使用第二种方法 int result = EvaluateOutcome(rank1, rank2);由于未分配的 rank1,rank2,放置在 Parallell Invoke 之后无法编译。可以在声明它们的地方将每个初始化为零吗?
  • 奇怪的是,这两种方法都使我的代码运行速度慢了大约 15 倍。任何想法为什么会这样?
  • @SteveW 是的,必须为 Parallel.Invoke 版本初始化变量才能编译。在这个例子中初始化它们是安全的,但它肯定会降低代码的健壮性。未来的修改可能更容易引入错误。老实说,我不知道如何在这方面对其进行鲁棒化。
  • @SteveW 至于并行版本比较慢,可能是因为整体工作太轻量了。查看this 了解更多详情。
猜你喜欢
  • 2018-03-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多