Ваша проблема вопиет о шаблоне производитель-потребитель: производитель производит ввод с другой скоростью, чем потребитель потребляет ввод: иногда быстрее, чем потребитель, иногда медленнее, чем потребитель.
Для этого вы ' Я буду использовать пакет nuget Microsoft TPL Dataflow
По сути, у вас есть один или несколько блоков источника, которые генерируют данные и помещают их в буфер. Один или несколько блоков-получателей ожидают поступления данных в буфер и обрабатывают их. При желании потребитель может произвести обработанные данные и поместить их в другой буфер, где другие потребители слушают.
Если источник завершил создание данных, он уведомляет буфер. Все потребители буфера знают, что больше не следует ожидать данных, и если они также являются производителями, они уведомляют свой выходной буфер о том, что больше ничего не следует ожидать.
Источник читает пользователя ввод, пока пользователь не уведомит об остановке (пустая строка). Все входные данные отправляются в буфер без ожидания (Post). Источник немедленно прочитает следующую строку.
class UserInputProducer
{
// Reads user input until user enters empty line
// User input is sent to output buffer
public async Task ProduceAsync()
{
string userInput = Console.ReadLine();
while (!String.IsNullOrEmpty(userInput))
{
// send the user input to the output buffer
this.producedUserInput.Post(userInput);
userInput = Console.ReadLine();
}
// no more inpu expected:
this.ProducedUserInput.Complete();
}
public ITargetBlock<string> ProducedUserInput {get; set;} = DataflowBlock<string>.NullTarget;
}
ProducedUserInput инициализируется с помощью NullTarget. Пока это не изменилось, то, очевидно, никто не заинтересован в моем произведенном выводе.
Потребитель будет ожидать входные строки и обрабатывать их.
Я не уверен, что вы хотели сделать с помощью списка команд. Поэтому я сделаю потребителя немного интереснее. Для этого примера у Потребителя есть два выхода: один со строкой «Вы ввели ...» и один с DateTimes, который вы использовали для добавления в CommandsList. Фактически, этот Потребитель также является Производителем.
class CommandProcessor
{
public IDataFlowBlock<string> Source {get; } = new BufferBlock<string>();
public ITargetBlock<string> YouTypedTarget<string> {get; set;} = DataFlowBlock.NullTarget<string>;
public ITargetBlock<DateTime> CommandTimeTarget {get; set;} = DataFlowBlock.NullTarget<DateTime>;
public async Task ProcessInput()
{
// wait for source output available:
while (await this.Source.OutputAvailableAsync())
{
// fetch the input and the time that this input was received;
string dataToProcess = await this.Source.ReceiveAsync();
DateTime time = DateTime.UtcNow;
// process the output to the two Destinations
this.YouTypedTarget.Post("You typed "+ dataToProcess);
this.CommandTimeTarget.Post(time);
}
// if here: no data to produce anymore
this.YouTypedTarget.Complete();
this.CommandTimeTarget.Complete();
}
}
Вам необходимо два Потребителя: один для вывода YouTyped и один для CommandTime. Я дам один:
class YouTypedConsumer
{
public IDataFlowBlock<string> Source {get; } = new BufferBlock<string>();
public async Task ConsumeAsync()
{
// wait for source output available:
while (await this.Source.OutputAvailableAsync())
{
// fetch the input and display it on the console
string txtToDisplay= await this.Source.ReceiveAsync();
Console.WriteLine(txtToDisplay);
// this Consumer does not produce anything
}
}
}
CommandTimeConsumer похож.
T ie все вместе:
var youTypedConsumer = new YouTypedConsumer();
var conmmandTimeConsumer = new CommandTimeConsumer();
// youTypedTarget of the command processor is input youTypedConsumer
var commandProcessor = new CommandProcessor
{
YouTypedTarget = youTypedConsumer.Source,
CommandTimeTarget = commandTimeConsumer.Source,
}
var userInputProducer = new UserInputProducer
{
ProducedUserInput = commandProcessor.Source,
};
Таким образом, userInputProducer отправляет полученные данные Исходнику команды Processor. CommandProcessor обрабатывает ввод и отправляет результат источнику youTypedConsumer и источнику commandTimeConsumer.
Теперь запустите все asyn c и дождитесь завершения всех задач:
var tasks = new Task[]
{
youTypedConsumer.ConsumeAsync(),
commandTimeConsumer.ConsumeAsync(),
commandProcessor.ProcessAsync(),
userInputProducer.ProduceAsync(),
};
// await until all tasks are finished:
await Task.WhenAll(tasks);
// or if this procedure is not async:
Task.WaitAll(tasks);
Приятно то, что у каждого блока есть небольшая задача. Легко понять задачу. Легко использовать для других конфигураций, например, для модульного тестирования. Нет проблем, если кто-то из производителей или потребителей иногда работает немного медленнее или быстрее, чем один блок до или после него. И это полностью асин c.