В моем приложении-изготовителе-потребителе я определил BufferBlock как очередь для добавления элементов.
public static BufferBlock<AppointmentReminder> m_Queue = new BufferBlock<AppointmentReminder>();
SemaphoreSlim seaphore = new SemaphoreSlim(4);
Затем, чтобы добавить элементы в очередь, у меня есть
private static void Producer()
{
for (int i = 0; i < 5000; i++)
{
AppointmentReminder reminder = new AppointmentReminder();
reminder.UniqueId = Guid.NewGuid();
reminder.CallMethod = "Number";
reminder.sString = "1234567890";
m_Queue.Post(reminder);
}
for (int i = 0; i < 3000; i++)
{
AppointmentReminder reminder = new AppointmentReminder();
reminder.UniqueId = Guid.NewGuid();
reminder.CallMethod = "Letter";
reminder.sString = "abcdefghij";
m_Queue.Post(reminder);
}
for (int i = 0; i < 2000; i++)
{
AppointmentReminder reminder = new AppointmentReminder();
reminder.UniqueId = Guid.NewGuid();
reminder.CallMethod = "Mixed";
reminder.sString = "abcd12345y";
m_Queue.Post(reminder);
}
Console.WriteLine("There are {0} items in the queue.\n", m_Queue.Count);
}
Теперь мне приходится иметь дело с потребительской частью. Для этого существует метод RunScript(AppointmentReminder callData)
. Это означает, что нам нужно вызвать метод в потребительской части, если доступно изделие. Но есть ограничение дросселирования. Максимальное количество элементов обработки - 4 в любое время.
Так что я:
private async static Task Consumer()
{
try
{
while (await m_Queue.OutputAvailableAsync())
{
AppointmentReminder reminder = m_Queue.Receive();
Call d = new Call();
d.RunScript(reminder);
}
}
catch (NullReferenceException ex)
{
Console.WriteLine("NullReferenceException: " + ex.Message);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
А для гоночного производителя и потребителя,
static void Main(string[] args)
{
AliveEvent = new ManualResetEvent(false);
Producer();
var consumer = Consumer();
consumer.Wait();
}
Мой вопрос в том, что я не силен в параллельной библиотеке задач (TPL). Как применить ограничение дросселирования к потребителю?
Редактировать: 03 октября 2014:
На основе решения svick. Код для потребителя:
private async static Task Consumer()
{
try
{
while (await m_Queue.OutputAvailableAsync())
{
var consumerBlock = new ActionBlock<AppointmentReminder>(
remainder => new Call().RunScript(remainder),
new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 4 });
m_Queue.LinkTo(
consumerBlock, new DataflowLinkOptions { PropagateCompletion = true });
m_Queue.Complete();
consumerBlock.Completion.Wait();
}
// m_Queue is a static BufferBlock in the original code.
}
Лучшим вариантом, чем самим написанием потребителя, является создание ActionBlock
, который уже поддерживает ограничение параллелизма:
var consumerBlock = new ActionBlock<AppointmentReminder>(
remainder => new Call().RunScript(remainder),
new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 4 });
И затем свяжите его с очередью:
queue.LinkTo(
consumerBlock, new DataflowLinkOptions { PropagateCompletion = true });
Наконец, дождитесь его завершения:
queue.Complete();
consumerBlock.Completion.Wait();
ActionBlock
как я и предлагал, вам больше не нужен цикл.
while (await m_Queue.OutputAvailableAsync())
теперь полностью избыточен - вы можете от него избавиться. Во-вторых, если ваш потребитель являетсяasync Task
, вы можете такжеawait consumerBlock.Completion
вместо блокировки с помощьюCompletion.Wait()
. В-третьих,m_Queue.Complete()
действительно принадлежит производителю (сразу после всехPost
), а не потребителю.m_Queue.Complete()
? Потому что в реальном случае элементы будут добавляться в цикле while. Это означает, что вы всегда будете добавлять элемент в очередь и никогда не останавливаться.