当迭代方法调用的结果时,Parallel.Foreach将如何表现?

时间:2015-02-19 17:29:01

标签: c# multithreading amazon-web-services parallel-processing

范围:

我目前正在实施一个使用Amazon SQS Service作为此程序要处理的数据提供者的应用程序。

由于我需要对从此队列中出列的消息进行并行处理,这就是我所做的。

Parallel.ForEach (GetMessages (msgAttributes), new ParallelOptions { MaxDegreeOfParallelism = threadCount }, message =>
        {
             // Processing Logic
        });

这是" GetMessages"的标题。方法:

    private static IEnumerable<Message> GetMessages (List<String> messageAttributes = null)
    {
        // Dequeueing Logic... 10 At a Time

        // Yielding the messages to the Parallel Loop
        foreach (Message awsMessage in messages)
        {
           yield return awsMessage;
        }
    }

这将如何运作?:

我最初的想法是,只要线程没有工作(或者很多线程没有工作,就像内部启发式一样),GetMessages方法就会执行测量这个)。对我来说,GetMessages方法会将消息分发给Parallel.For工作线程,这将处理消息并等待Parallel.For handler向他们提供更多消息工作

问题?我错了......

事情是,我错了。不过,我不知道在这种情况下发生了什么。

出列的邮件数量太高,每次出列时都会以2的幂出现。出列计数(消息)如下:

  1. Dequeue被调用:返回80条消息
  2. Dequeue被调用:返回160条消息
  3. Dequeue被调用:返回320条消息(依此类推)
  4. 在某一点之后,出列的消息数量,或者,在这种情况下等待我的应用程序处理的消息数量太高,我最终耗尽了内存。

    更多信息:

    我正在使用线程安全的InterLocked操作来增加上面提到的计数器。

    正在使用的线程数为25(对于Parallel.Foreach

    每个&#34; GetMessages&#34;将返回最多10条消息(作为IEnumerable,已经产生)。

    问题:这种情况究竟发生了什么?

    我正在努力弄清楚到底发生了什么。每个线程完成&#34;处理循环&#34;后,我的GetMessages方法是否被调用,从而导致越来越多的消息被出列?

    是由单个线程调用&#34; GetMessages&#34;还是由多个线程调用?

1 个答案:

答案 0 :(得分:1)

我认为Parallel.ForEach分区存在问题...您的问题是典型的生产者/消费者情景。对于这种情况,你应该有一个单独出列的独立逻辑,另一方面进行处理。它将尊重关注点的分离,并将简化调试。

BlockingCollection<T>将允许您将两者分开:一方面,您添加要处理的项目,另一方面,您使用它们。以下是如何实现它的示例:

在处理方法中,BlockingCollection<T>工作负载分区(.GetConsumingEnumerable())需要ParallelExtensionsExtras nuget包。

public static class ProducerConsumer
{
    public static ConcurrentQueue<String> SqsQueue = new ConcurrentQueue<String>();         
    public static BlockingCollection<String> Collection = new BlockingCollection<String>();
    public static ConcurrentBag<String> Result = new ConcurrentBag<String>();

    public static async Task TestMethod()
    {
        // Here we separate all the Tasks in distinct threads
        Task sqs = Task.Run(async () =>
        {
            Console.WriteLine("Amazon on thread " + Thread.CurrentThread.ManagedThreadId.ToString());
            while (true)
            {
                ProducerConsumer.BackgroundFakedAmazon(); // We produce 50 Strings each second
                await Task.Delay(1000);
            }
        });
        Task deq = Task.Run(async () =>
        {
            Console.WriteLine("Dequeue on thread " + Thread.CurrentThread.ManagedThreadId.ToString());
            while (true)
            {
                ProducerConsumer.DequeueData(); // Dequeue 20 Strings each 100ms 
                await Task.Delay(100);
            }
        });

        Task process = Task.Run(() =>
        {
            Console.WriteLine("Process on thread " + Thread.CurrentThread.ManagedThreadId.ToString());
            ProducerConsumer.BackgroundParallelConsumer(); // Process all the Strings in the BlockingCollection
        });

        await Task.WhenAll(c, sqs, deq, process);
    }

    public static void DequeueData()
    {
        foreach (var i in Enumerable.Range(0, 20))
        {
            String dequeued = null;
            if (SqsQueue.TryDequeue(out dequeued))
            {
                Collection.Add(dequeued);
                Console.WriteLine("Dequeued : " + dequeued);
            }
        }
    }

    public static void BackgroundFakedAmazon()
    {
        Console.WriteLine(" ---------- Generate 50 items on amazon side  ---------- ");
        foreach (var data in Enumerable.Range(0, 50).Select(i => Path.GetRandomFileName().Split('.').FirstOrDefault()))
            SqsQueue.Enqueue(data + " / ASQS");
    }

    public static void BackgroundParallelConsumer()
    {
        // Here we stay in Parallel.ForEach, waiting for data. Once processed, we are still waiting the next chunks
        Parallel.ForEach(Collection.GetConsumingEnumerable(), (i) =>
        {
            // Processing Logic
            String processedData = "Processed : " + i;
            Result.Add(processedData);
            Console.WriteLine(processedData);
        });

    }
}

您可以从以下控制台应用中尝试:

static void Main(string[] args)
{
    ProducerConsumer.TestMethod().Wait();
}