First of all:
You only benefit from async-await if your program has something else
to do while your tasks are running.
If your main thread would start a task, and do nothing but wait for this task to finish, your main thread could do the work himself. That would even be faster.
In your example, I can imagine that sending over the serial line is significantly slower than your processing. So I can imagine that while one thread is busy sending data over the serial line, your thread can be busy creating the next data that is to be sent. Or maybe 10 threads are creating data that is to be sent one after another. Of course in the latter case it is not guaranteed in which order the data will be sent.
Buf let's see it simpler: one thread is creating data in its own speed, while another thread is sending data independently over the serial line.
This screams for a producer - consumer pattern: one thread is the producer, it produces items that the consumer reads and processes. After a while the producer tells the consumer that no data is to be expected anymore.
The key object in this is System.Threading.Tasks.Dataflow.BufferBlock. See MSDN. The remarks section says that it is distributed via nuget.
The bufferBlock implements two interfaces:
- ITargetBlock
<T
> for the producer to send its output to
- ISourceBlock
<T
> for the consumer to read the input from.
Let's assume you use System.IO.Ports.SerialPort to send your data. Alas this class has no async support, so we have to create it ourselves. Let's assume you want to convert objects of type T into a format that can be sent over the serial line. Code would look like follows:
private void Write(T t)
{
var dataToSend = ConvertToData(t);
serialPort.Write(dataToSend);
}
Not very async is it. So let's make an async function ofit:
private async Task WriteAsync(T t)
{
return await Task.Run ( () =>
{
var dataToSend = ConvertToData(t);
serialPort.Write(dataToSend);
}
}
Or you could just call the other write function:
return await Task.Run ( () => Write(t));
Note: if you make sure there is only one thread that will use this function, you don't have to lock it.
Now that we do have an async function to send objects of type T over the serial line, let's create a producer that will create objects of type T and send them to the bufferblock.
I'll make it async, so the calling thread can do other things while data is being produced:
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();
}
The receiving side will be done by a different thread:
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
}
Now all we need is one procedure that creates the two threads and waits until both tasks are finished:
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});
}
Note: because of the bufferBlock it is no problem that the producer is
already producing while the consumer is not started yet.
All we need is a function that starts the async, if you have an event handler just declare it async:
private async void OnButton1_clicked(object sender, ...)
{
await ProduceConsumeAsync()
}
If you have no async function, you have to create a task yourself:
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;
}
More information about the consumer - producer pattern. See MSDN
How to: Implement a Producer-Consumer Dataflow Pattern