【问题标题】:C# ForEach Loop With ASync Tasks & Dependent Post ASync TasksC# ForEach 循环,带有异步任务和依赖的后异步任务
【发布时间】:2023-03-16 00:34:01
【问题描述】:

我无法正确构建最有效的方法来迭代从请求对象启动的多个异步任务,然后执行一些其他异步任务,这些任务同时依赖于请求对象和第一个异步任务的结果。我在 AWS 中运行 C# lambda 函数。我已经尝试过这样的模型(为简洁起见,省略了错误处理等):

public async Task MyAsyncWrapper()
{
  List<Task> Tasks = new List<Task>();
  foreach (var Request in Requests) 
  {
    var Continuation = this.ExecuteAsync(Request).ContinueWith(async x => {
      var KeyValuePair<bool, string> Result = x.Result;
      if (Result.Key == true)
      {
        await this.DoSomethingElseAsync(Request.Id, Request.Name, Result.Value);
        Console.WriteLine("COMPLETED");
      }
    }

    Tasks.Add(Continuation);
  }

  Task.WaitAll(Tasks.ToArray());
}

这种方法导致 DoSomethingElseAsync() 方法并没有真正得到等待,并且在我的许多 Lambda 函数调用中,我从未得到“COMPLETED”输出。我也用这种方法解决了这个问题:

public async Task MyAsyncWrapper()
{
  foreach (var Request in Requests) 
  {
    KeyValuePair<bool, string> Result = await this.ExecuteAsync(Request);

    if (Result.Key == true)
    {
      await this.DoSomethingElseAsync(Request.Id, Request.Name, Result.Value);
      Console.WriteLine("COMPLETED");
    }
  }
}

这可行,但我认为这很浪费,因为我只能在等待 asnyc 完成时执行循环的一次迭代。我也引用了Interleaved Tasks,但问题是我基本上有两个循环,一个用于填充任务,另一个用于在它们完成后对其进行迭代,我无法再访问原始的Request 对象.所以基本上是这样的:

List<Task<KeyValuePair<bool, string>>> Tasks = new List<Task<KeyValuePair<bool, string>>>();

foreach (var Request in Requests)
{
  Tasks.Add(ths.ExecuteAsync(Request);
}

foreach (Task<KeyValuePair<bool, string>> ResultTask in Tasks.Interleaved())
{
  KeyValuePair<bool, string> Result = ResultTask.Result;
  //Can't access the original request for this method's parameters
  await this.DoSomethingElseAsync(???, ???, Result.Value);
}

对于在 foreach 循环中实现这种类型的异步链接的更好方法有什么想法吗?我的理想方法不是将请求对象作为来自ExecuteAsync() 的响应的一部分返回,所以如果可能的话,我想尝试寻找其他选项。

【问题讨论】:

    标签: c# asynchronous async-await parallel-foreach


    【解决方案1】:

    我可能会误解,但为什么不将您的“迭代”移到它自己的函数中,然后使用Task.WhenAll 并行等待所有迭代。

    public async Task MyAsyncWrapper()
    {
      var allTasks = Requests.Select(ProcessRequest);
    
      await Task.WhenAll(allTasks);
    }
    
    private async Task ProcessRequest(Request request)
    {
        KeyValuePair<bool, string> Result = await this.ExecuteAsync(request);
    
        if (Result.Key == true)
        {
          await this.DoSomethingElseAsync(request.Id, request.Name, Result.Value);
          Console.WriteLine("COMPLETED");
        }
    }
    

    【讨论】:

    • 我认为这可能真的很有帮助。我会尝试一下,看看它是否有效。
    • 这个解决方案对我有用,并且显着减少了我为在多种不同请求类型上执行这些循环而编写的代码量。
    【解决方案2】:

    考虑使用 TPL 数据流:

    var a = new TransformBlock<Input, OutputA>(async Input i=>
    {
        // do something async.
        return new OutputA();
    });
    
    var b = new TransformBlock<OutputA, OutputB>(async OutputA i =>
    {
        // do more async.
        return new OutputB();
    });
    
    var c = new ActionBlock<OutputB>(async OutputB i =>
    {
        // do some final async.
    });
    
    a.LinkTo(b, new DataflowLinkOptions { PropogateCompletion = true });
    b.LinkTo(c, new DataflowLinkOptions { PropogateCompletion = true });
    
    // push all of the items into the dataflow.
    a.Post(new Input());
    a.Complete();
    
    // wait for it all to complete.
    await c.Completion;
    

    【讨论】:

    • 所以我看到了这对于单个动作链是如何工作的,但是在 InputRequest 的 foreach 循环中它会按预期工作,这样我就可以确保每个c在退出方法前完成了吗?
    • 这将替换 foreach 循环:您的循环不会将您的代码放在循环中,而只是将 Post 数据放入数据流中。
    猜你喜欢
    • 2017-09-30
    • 1970-01-01
    • 1970-01-01
    • 2014-04-19
    • 1970-01-01
    • 2014-07-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多