使用Parallel.For时出现不可预知的结果

时间:2016-02-19 06:53:31

标签: c# amazon-web-services task-parallel-library amazon-sqs parallel.foreach

我正在运行Parallel for循环,它最初运行时间=处理器数量并执行长时间运行操作。完成后的每个任务,检查更多任务,如果找到,再次调用自己。

以下是我的代码的样子:

static void Main(string[] args)
{
   Int32 numberOfProcessors = Environment.ProcessorCount;

   Parallel.For(0, numberOfProcessors, index => DoSomething(index, sqsQueueURL));

}

private async static Task DoSomething(int index, string queueURL)
{
   var receiveMessageRequest = new ReceiveMessageRequest { QueueUrl = queueURL, WaitTimeSeconds = 20, MaxNumberOfMessages = 1, VisibilityTimeout = 1200 };

   AmazonSQSClient sqsClient = new AmazonSQSClient(new AmazonSQSConfig { MaxErrorRetry = 4 });

   var receiveMessageResponse = sqsClient.ReceiveMessage(receiveMessageRequest);

   foreach (var msg in receiveMessageResponse.Messages)
   {
      PerformALongRunningTask......

      //delete the message

      DeleteMessageRequest deleteMessageRequest = new DeleteMessageRequest(queueURL, msg.ReceiptHandle);

      AmazonSQSClient sqsDeleteClient = new AmazonSQSClient();

      sqsDeleteClient.DeleteMessage(deleteMessageRequest);

      //Do it again
      DoSometing(index,queueURL)

   }
}

我的结果非常难以预测。它永远不会完成所有任务。它在完成所有事情之前退出。

我在这里做错了什么?

更短的代码:

static Int32 TimesToLoop = 143;
static void Main(string[] args)
{

    Int32 numberOfProcessors = Environment.ProcessorCount;

    Parallel.For(0, numberOfProcessors, index => DoSomething(index));

    Console.Read();
}

private async static Task DoSomething(int index)
{
    if(TimesToLoop == 0)
    {
        return;
    }
    Console.WriteLine(index);
    Interlocked.Decrement(ref TimesToLoop);
    DoSomething(index++);
    return;

}

2 个答案:

答案 0 :(得分:3)

我现在看到各种问题:

  • Parallel.For刚刚开始执行任务。它不会等待它们完成。它会等待DoSomething方法调用返回,但是它们会返回代表异步操作的任务,这些操作可能无法同步完成。
  • 正如CarbineCoder所说,你的递归几乎肯定是有缺陷的。它不清楚你想要实现的目标,但你需要重新思考这个方面
  • 你的递归并不是await任务返回的任务 - 它几乎肯定应该。 可能想要创建在foreach循环中创建的所有任务的集合,并一次性等待它们,或者它可能立即await它们。我们无法说出来。

修复第一部分的最简单方法可能是使用Task.WaitAll代替Parallel.For

var tasks = Enumerable.Range(0, numberOfProcessors)
                      .Select(index => DoSomething(index, sqsQueueURL))
                      .ToList();
Task.WaitAll(tasks);

Task.WhenAll不同,Task.WaitAll将阻止,直到完成所有指定的任务。请注意,如果任何任务需要在调用WaitAll的线程上继续执行,这是安全,正是因为它阻止了 - 但是如果这是一个控制台应用程序并且您是从初始线程重新调用它,你可以正常,因为延续将在线程池上执行。

答案 1 :(得分:1)

private async static Task DoSomething(int index, string queueURL)
{
   ...
   foreach (var msg in receiveMessageResponse.Messages)
   {
      ...
      //Do it again
      DoSometing(index,queueURL)

   }
}

您正在递归地调用DoSomething并且没有条件可以打破/退出它。它可能会导致stackoverflow并终止你的程序。