Прежде всего:
Вы получаете выгоду от async-await только в том случае, если вашей программе есть что делать во время выполнения ваших задач.
Если бы ваш основной поток запускал задачу и ничего не делал, кроме как ждал завершения этой задачи, ваш основной поток мог бы выполнить эту работу сам. Это было бы даже быстрее.
В вашем примере я могу представить, что отправка по последовательной линии значительно медленнее, чем ваша обработка. Поэтому я могу представить, что пока один поток занят отправкой данных по последовательной линии, ваш поток может быть занят созданием следующих данных, которые должны быть отправлены. Или, может быть, 10 потоков создают данные, которые должны быть отправлены один за другим. Конечно, в последнем случае не гарантируется порядок отправки данных.
Но давайте посмотрим на это проще: один поток создает данные на своей скорости, а другой поток независимо отправляет данные по последовательной линии.
Это говорит о модели производитель-потребитель: один поток является производителем, он производит элементы, которые считывает и обрабатывает потребитель. Через некоторое время производитель сообщает потребителю, что никаких данных больше не ожидается.
Ключевым объектом в этом является System.Threading.Tasks.Dataflow.BufferBlock. См. MSDN. В разделе примечаний говорится, что он распространяется через nuget.
В bufferBlock реализованы два интерфейса:
- ITargetBlock
<T
>, чтобы производитель отправлял свои выходные данные
- ISourceBlock
<T
>, чтобы потребитель мог читать входные данные.
Предположим, вы используете System.IO.Ports.SerialPort для отправки данных. Увы, у этого класса нет поддержки асинхронности, поэтому нам придется создать его самостоятельно. Предположим, вы хотите преобразовать объекты типа T в формат, который можно отправить по последовательной линии. Код будет выглядеть следующим образом:
private void Write(T t)
{
var dataToSend = ConvertToData(t);
serialPort.Write(dataToSend);
}
Это не очень асинхронно. Итак, давайте сделаем из него асинхронную функцию:
private async Task WriteAsync(T t)
{
return await Task.Run ( () =>
{
var dataToSend = ConvertToData(t);
serialPort.Write(dataToSend);
}
}
Или вы можете просто вызвать другую функцию записи:
return await Task.Run ( () => Write(t));
Примечание: если вы убедитесь, что только один поток будет использовать эту функцию, вам не нужно его блокировать.
Теперь, когда у нас есть асинхронная функция для отправки объектов типа T по последовательной линии, давайте создадим производителя, который будет создавать объекты типа T и отправлять их в буферный блок.
Я сделаю это асинхронным, чтобы вызывающий поток мог заниматься другими делами, пока генерируются данные:
private BufferBlock<T> bufferBlock = new BufferBlock<T>();
private async Task ProduceAsync()
{
while (objectsToProcessAvailable())
{
T nextObject = GetNextObjectToProcess()
await bufferBlock.SendAsync(nextObject);
}
// nothing to process anymore: mark complete:
bufferBlock.Complete();
}
Принимающая сторона будет выполняться другим потоком:
private Task ConsumeAsync()
{
// as long as there is something to process: fetch it and process it
while (await bufferBlock.OutputAvailableAsync())
{
T nextToProcess = await bufferBlock.ReceiveAsync();
// use WriteAsync to send to the serial port:
await WriteAsync(nextToProcess);
}
// if here: no more data to process. Return
}
Теперь все, что нам нужно, — это одна процедура, которая создает два потока и ждет завершения обеих задач:
private async Task ProduceConsumeAsync()
{
var taskProducer = ProduceAsync();
// while the producer is busy producing, you can start the consumer:
var taskConsumer = ConsumeAsync();
// while both tasks are busy, you can do other things,
// like keep the UI responsive
// after a while you need to be sure the tasks are finished:
await Task.WhenAll(new Task[] {taskProducer, taskConsumer});
}
Примечание: из-за bufferBlock не проблема, что производитель уже производит, а потребитель еще не запущен.
Все, что нам нужно, это функция, которая запускает асинхронность, если у вас есть обработчик событий, просто объявите его асинхронным:
private async void OnButton1_clicked(object sender, ...)
{
await ProduceConsumeAsync()
}
Если у вас нет асинхронной функции, вам нужно создать задачу самостоятельно:
private void MyFunction()
{
// start produce consume:
var myTask = Task.Run( () => ProduceConsumeAsync());
// while the task is running, do other things.
// when you need the task to finish:
await myTask;
}
Дополнительные сведения о шаблоне потребитель-производитель. См. MSDN
Как реализовать шаблон потока данных "производитель-потребитель" а>
person
Harald Coppoolse
schedule
10.08.2015