Task.Whenall(List <task>)输出OutOfRangeException

时间:2018-10-21 03:17:29

标签: c#

我有两个使用SemaphoreSlim的循环和一个字符串“ Contents”的数组

一个foreachloop:

        var allTasks = new List<Task>();
        var throttle = new SemaphoreSlim(10,10);
        foreach (string s in Contents)
        {
            await throttle.WaitAsync();
            allTasks.Add(
                Task.Run(async () =>
                {
                    try
                    {
                        rootResponse.Add(await POSTAsync(s, siteurl, src, target));
                    }
                    finally
                    {
                        throttle.Release();
                    }
                }));
        }
        await Task.WhenAll(allTasks);

一个for循环:

        var allTasks = new List<Task>();
        var throttle = new SemaphoreSlim(10,10);
        for(int s=0;s<Contents.Count;s++)
        {
            await throttle.WaitAsync();
            allTasks.Add(
                Task.Run(async () =>
                {
                    try
                    {
                        rootResponse[s] = await POSTAsync(Contents[s], siteurl, src, target);
                    }
                    finally
                    {
                        throttle.Release();
                    }
                }));
        }
        await Task.WhenAll(allTasks);

第一个foreach循环运行良好,但是for循环Task.WhenAll(allTask​​s)返回一个OutOfRangeException,我希望Contents []索引和List索引匹配。

我可以修复for循环吗?还是有更好的方法?

1 个答案:

答案 0 :(得分:2)

这将解决您当前的问题

for (int s = 0; s < Contents.Count; s++)
{
   var content = Contents[s];

   allTasks.Add(
      Task.Run(async () =>
                  {
                     await throttle.WaitAsync();
                     try
                     {
                        rootResponse[s] = await POSTAsync(content, siteurl, src, target);
                     }
                     finally
                     {
                        throttle.Release();
                     }
                  }));
}
await Task.WhenAll(allTasks);

但是,这是一段相当凌乱和讨厌的代码。看起来有点整洁

public static async Task DoStuffAsync(Content[] contents, string siteurl, string src, string target)
{
   var throttle = new SemaphoreSlim(10, 10);

   // local method
   async Task<(Content, SomeResponse)> PostAsyncWrapper(Content content)
   {
      await throttle.WaitAsync();
      try
      {
         // return a content and result pair
         return (content, await PostAsync(content, siteurl, src, target));
      }
      finally
      {
         throttle.Release();
      }   
   }

   var results = await Task.WhenAll(contents.Select(PostAsyncWrapper));

   // do stuff with your results pairs here
}

还有许多其他方法可以执行此操作,PLinqParallel.ForParallel.ForEach,或者只是像上面那样在循环中整理捕获的内容。

但是,由于您有IO约束的工作负载,并且您有async个方法来运行它。最合适的解决方案是asyncawait都无法最佳地满足需求的Parallel.For Parallel.ForEach模式。

另一种方法是在TPL DataFlow软件包中找到System.Threading.Tasks.Dataflow nuget库。

代码

public static async Task DoStuffAsync(Content[] contents, string siteurl, string src, string target)
{

   async Task<(Content, SomeResponse)> PostAsyncWrapper(Content content)
   {
      return (content, await PostAsync(content, siteurl, src, target));
   }

   var bufferblock = new BufferBlock<(Content, SomeResponse)>();
   var actionBlock = new TransformBlock<Content, (Content, SomeResponse)>(
      content => PostAsyncWrapper(content),
      new ExecutionDataflowBlockOptions
         {
            EnsureOrdered = false,
            MaxDegreeOfParallelism = 100,
            SingleProducerConstrained = true
         });
   actionBlock.LinkTo(bufferblock);

   foreach (var content in contents)
      actionBlock.Post(content);

   actionBlock.Complete();
   await actionBlock.Completion;

   if (bufferblock.TryReceiveAll(out var result))
   {
      // do stuff with your results pairs here   
   }

}

基本上,这会创建一个BufferBlockTransformBlock,您将工作负载泵入TransformBlock,它的选项具有并行度,并将它们推入{{1 }},您等待完成并获得结果。

为什么要进行数据流处理?因为它处理BufferBlock async,所以它具有await,它是为IO绑定或CPU绑定的工作负载而设计的,并且非常易于使用。此外,由于通常以多种方式(在管道中)处理大多数数据,因此您可以使用它来按顺序,并行或以任何方式选择管道来管道化和操纵数据流。

无论如何,祝你好运