Преди всичко:
Вие се възползвате само от 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 не е проблем, че производителят вече произвежда, докато потребителят все още не е стартиран.
Всичко, от което се нуждаем, е функция, която стартира async, ако имате манипулатор на събития, просто го декларирайте async:
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