【问题标题】:Parallel execution of async Task<T> methods in loop [closed]在循环中并行执行异步 Task<T> 方法[关闭]
【发布时间】:2023-03-02 22:43:01
【问题描述】:

我有一个带有async Task&lt;Result&gt; 方法的程序,而不是我需要从一个循环中并行运行。 这是我尝试过的两种方法,第一种(ResolveWidgetsTaskFactoryAsync)按预期工作,但另一种(ResolveWidgetsNotParallelAsync)同步运行,即使每个方法都作为任务返回。 在保持异步 Task&lt;string&gt; 返回类型为 ComputeAsync 的同时,我怎样才能更好地编写并行版本?

void Main()
{
    var results = ResolveWidgetsTaskFactoryAsync().Result;
    results.Dump();

    results = ResolveWidgetsNotParallelAsync().Result;
    results.Dump();
}

/// <summary>
/// Runs synchronously instead of in parallel
/// /summary>
private async Task<IEnumerable<string>> ResolveWidgetsNotParallelAsync()
{
    var values = new[] { "a", "b", "c" };
    var widgets = new List<Task<string>>();

    foreach (var value in values)
    {
        Console.WriteLine("Iteration value : " + value);

        var widgetTask = ComputeAsync(value);
        widgets.Add(widgetTask);
    }

    return await Task.WhenAll(widgets);
}

/// <summary>
/// Runs as expected, but I'm not sure it's well written
/// /summary>
private async Task<IEnumerable<string>> ResolveWidgetsTaskFactoryAsync()
{
    var values = new[] { "a", "b", "c" };
    var widgets = new List<Task<string>>();

    foreach (var value in values)
    {
        Console.WriteLine("Iteration value : " + value);

        widgets.Add(Task<string>.Factory.StartNew(() => ComputeAsync(value).Result));
    }

    return await Task.WhenAll(widgets);
}

public async Task<string> ComputeAsync(string value)
{
    try
    {
        Console.WriteLine("Processing value : " + value);

        Random rnd = new Random();
        var wait = rnd.Next(1000, 5000);
        Console.WriteLine("Wait : " + wait);
        Thread.Sleep(wait);

        return await Task.FromResult(value + " done");
    }
    catch (Exception e)
    {
        return null;
    }
}

【问题讨论】:

  • 因为ComputeAsync中没有任何异步
  • Thread.Sleep(wait); 替换为 await Task.Delay(wait); ,然后重试。
  • 不要将Task&lt;T&gt;Thread.Sleep(wait); 混用,这一行也没有异步widgets.Add(Task&lt;string&gt;.Factory.StartNew(() =&gt; ComputeAsync(value).Result)); Result 阻塞调用
  • Task&lt;string&gt;.Factory.StartNew(() =&gt; ComputeAsync(value).Result) 这在很多方面都很糟糕。为什么要为已经基于任务的方法调用 Factory.StartNew?你为什么要使用阻塞调用.Result?你应该这样做 widgets.Add(ComputeAsync(value)); 并摆脱 Thread.Sleep 以支持 Task.Delay
  • 请注意,parallelasync 是两个完全不同的概念。两者都不暗示对方。

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


【解决方案1】:

要以更好的方式编写并行版本,您应该首先意识到您的代码本质上是同步的。它没有什么异步的。将同步代码包装在异步 API 中只会带来复杂性和混乱。所以我的建议是从你的代码中删除所有Taskasyncawait 关键字,并使用Parallel 类或PLINQ 库来并行化它。下面是一个 PLINQ 示例:

public string Compute(string value)
{
    //...
}

private string[] ResolveWidgetsParallel()
{
    var values = new[] { "a", "b", "c" };
    return values
        .AsParallel()
        .AsOrdered()
        .Select(value => Compute(value))
        .ToArray();
}

【讨论】:

  • 谢谢。我也知道这种方法,但我不得不保留“异步任务”,所以我想知道依靠异步 C# 功能编写的最佳方法。
  • @AFract 如果您能提供更多关于为什么需要为具有同步实现的方法维护异步签名的信息,那将会很有趣。这不是史无前例的,例如抽象类TextReader 使用它的ReadLineAsync 方法来实现它,但这旨在被该类的派生实现覆盖。一般来说,不遵守异步合同并强制调用者完成所有工作而不是返回不完整的任务被认为是不好的做法。
猜你喜欢
  • 2011-07-02
  • 1970-01-01
相关资源
最近更新 更多