This is basically a producer-consumer problem, so that should be the basis for the general design.
Here are some thoughts on that:
a) FIFO buffer (Queue)
First of all, you should have an instance of a thread-safe Queue (a FIFO buffer) for each instance of your class. One thread would receive the data and fill it, while the other one would read the data in a thread-safe manner. This only means you would have to use a lock on each enqueue/dequeue operation.
FIFO Queue would enable you to simultaneously process the data in the worker thread, while filling it from the communication thread. If you need to receive lots of data, you could dequeue some data in the worker thread and parse it before all of it has been received. Otherwise you would need to wait until all data has been received to parse it all at once. In most cases, you don't know how much data you are supposed to get, until you start to parse it.
b) Worker thread waiting for data
I would create a worker thread which would wait for a signal that new data has been received. You could use ManualResetEvent.WaitOne(timeOut)
to have a timeout in case nothing happens for a while. When the data is received, you would have to parse it, based on your current state -- so that would be an implementation of a state machine.
c) Port abstraction
To handle different types of ports, you could wrap your serial port inside an interface, which could have at least these methods (I might have forgotten something):
interface IPort
{
void Open();
void Close();
event EventHandler<DataEventArgs> DataReceived;
void Write(Data data);
}
This would help you separate the specific communication code from the state machine.
NOTE:
(according to Microsoft) The DataReceived event is not guaranteed to be raised for every byte received. Use the BytesToRead property to determine how much data is left to be read in the buffer. So you could create your own implementation of IPort which would poll SerialPort in regular intervals to ensure that you don't miss a byte (there is a question of SO which already addresses this).
d) Receiving data
To receive the data, you would have to attach a handler for the IPort.DataReceived
event (or SerialPort.DataReceived
, if you're not wrapping it), and enqueue the received data to the Queue inside the handler. In that handler you would also set the mentionel ManualResetEvent
to notify the worker thread that new data has been received.