范围:
我目前正在实施一个使用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的幂出现。出列计数(消息)如下:
在某一点之后,出列的消息数量,或者,在这种情况下等待我的应用程序处理的消息数量太高,我最终耗尽了内存。
更多信息:
我正在使用线程安全的InterLocked
操作来增加上面提到的计数器。
正在使用的线程数为25(对于Parallel.Foreach
)
每个&#34; GetMessages&#34;将返回最多10条消息(作为IEnumerable,已经产生)。
问题:这种情况究竟发生了什么?
我正在努力弄清楚到底发生了什么。每个线程完成&#34;处理循环&#34;后,我的GetMessages
方法是否被调用,从而导致越来越多的消息被出列?
是由单个线程调用&#34; GetMessages&#34;还是由多个线程调用?
答案 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();
}