I have C# multithreaded application that has to interface with a hardware using SerialPort.
The program is mostly command response sequence but the hardware can send an unsolicited “RESET” message due to an internal error at which time the software has to reinitialize it by sending a sequence of commands setting certain values.
More than one thread (from threadpool) can try to do a TakeSampleNow()
public class ALComm
{
private readonly AutoLoaderManager _manager;
private readonly AutoResetEvent dataArrived = new AutoResetEvent(false);
private SerialPort _alPort;
private string _alResponse;
.... Code to init _alPort and attach datareceived event etc
public void TakeSampleNow()
{
if (Monitor.TryEnter(_alPort, 1000)) //let's wait a second
{
_manager.MessageList.Enqueue("Try sampling");
try
{
Send("Command1");
string response = Receive();
switch(response)
{
case "X": blah blah..
case "Y": blah blah..
}
Send("Command2");
string response = Receive();
while(response != "OK")
{
Send("Command3");
string response = Receive();
Send("Command2");
string response = Receive();
}
}
finally
{
Console.WriteLine("Releasing port");
//Thread.CurrentThread.Priority = ThreadPriority.Normal;
Monitor.Exit(_alPort);
}
else
{
_manager.MessageList.Enqueue("Port is busy!!!");
}
}
public string Receive()
{
string inString = null;
dataArrived.WaitOne(1000);
inString = _alResponse;
return inString;
}
private void AlPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
_alResponse = _alPort.ReadLine();
//if (_alResponse.ToUpper().Contains("RESET"))
//{
// _alState = AlState.Reset;
// TryInitialize();
//}
dataArrived.Set();
}
private void TryInitialize()
{
Monitor.Enter(_alPort); //lock so other threads do not access samplenow during initialization
try
{
string response;
Console.WriteLine("Initializing ... The AutoLoader");
_alPort.DiscardInBuffer();
Send("CommandX");
response = Receive();
--- blah blah
_alResponse = "";
}
finally
{
Monitor.Exit(_alPort);
}
}
I can check the response in the datareceived event and wait on lock in TryInitialize() and for other threads in TakeSampleNow to release the lock, I would have to check on each response if _alResponse contains “RESET” and if it does return from the method. Which makes it more convoluted.
Any suggestions of how I can do this better. I believe it can be a state machine but am not able to conceptualize it.
You don’t supply much details of your protocol – you don’t say if command/rsponse pairs can overlap and, if so, how the responses are matched up with the commands.
You should be able to do this with a state-engine. Run the state-machine with its own thread that waits on a BlockingCollection for events. You will need a ‘SerialRecv’ thread as well to run your protocol and parse incoming bytes into messages.
I would use just one ‘SerialEvent’ class to carry events into the SM queue. The class should have an enum to describe the event and members for rx buffer, txData, parsed data, data to assemble tx string from, an exception/errorMess field – everything needed for any event or and forward purpose, (eg. the SM might forward a completed Request/Response to a display or logger).
Some events I can think of straightaway: EsmNewRequestResponse,EsmRxData,EsmResetRx
The event enum may, as some stages, have other values that are not used by the SM, eg: EsmError,EsmLog,EsmDisplay.
If you need timeouts, you can generate one by timing out the take() on the SM input queue.
Yes, there are things I left out.
If several threads issue SerialEvent instances ‘at once’, the SM will get new SerialEvents while it is still processing the first one. The SM will need another queue/deque to hold the SerialEvents awaiting handling. Due to the serializing of the SM by the BlockingCollection/thread, this ‘pending’ queue does not have to be thread-safe. The SM should check this pending queue after any request/response has completed to see if there is another one to process.
To handle request/response synchronously from several threads, the requesting threads must have smething to wait on. An AutoResetEvent in the SerialEvent class would do. Submitting a SerialEvent to the system would queue up the SerialEvent instance and wait on the AutoResetEvent. When the processing of the instance is complete, (ie. response received, error or timeout), the SM would set the event and the originating thread would run on with its SerialEvent instance filled in with data.
Next – the SerialEvent class is getting towards the point where it may be better to pool them rather than continually create/CG. That would need another BlockingCollection to act as the pool.