Reputation: 10201
I've been asked to write a method that will allow a caller to send a command string to a hardware device via the serial port. After sending the command the method must wait for a response from the device, which it then returns to the caller.
To complicate things the hardware device periodically sends unsolicited packets of data to the PC (data that the app must store for reporting). So when I send a serial command, I may receive one or more data packets before receiving the command response.
Other considerations: there may be multiple clients sending serial commands potentially at the same time as this method will form the basis of a WCF service. Also, the method needs to be synchronous (for reasons I won't go into here), so that rules out using a callback to return the response to the client.
Regarding the "multiple clients", I was planning to use a BlockingCollection<> to queue the incoming commands, with a background thread that executes the tasks one at a time, thus avoiding serial port contention.
However I'm not sure how to deal with the incoming serial data. My initial thoughts were to have another background thread that continually reads the serial port, storing data analysis packets, but also looking for command responses. When one is received the thread would somehow return the response data to the method that originally sent the serial command (which has been waiting ever since doing so - remember I have a stipulation that the method is synchronous).
It's this last bit I'm unsure of - how can I get my method to wait until the background thread has received the command's response? And how can I pass the response from the background thread to my waiting method, so it can return it to the caller? I'm new to threading so am I going about this the wrong way?
Thanks in advance
Andy
Upvotes: 2
Views: 595
Reputation: 24857
A couple things. You need to be able to tie up serial responses to the commands that requested them. I assume that there's some index or sequence number that goes out with the command and comes back in the response?
Given that, you should be OK. You need some sort of 'serialAPU' class to represent the request and response. I don't know what these are, maybe just strings, I don't know. The class should have an autoResetEvent as well. Anyway, in your 'DoSerialProtocol()' function, create a serialAPU, load it up with request data, queue it off to the serial thread and wait on the autoResetEvent. When the thread gets the serialAPU, it can store an index/sequence number in the serialAPU, store the serialAPU in a vector and send off the request.
When data comes in, do you protocol stuff and, if the data is a valid response, get the index/sequence from the data and look up the matching value in the serialAPU's in the vector. Remove the matching serialAPU from the vector, load it up with the response data and signal the autoResetEvent. The thread that called 'DoSerialProtocol()' originally will then run on and can handle the response data.
There are lots of 'wiggles' of course. Timeouts is one. I would be tempted to have a state enum in the serialAPU, protected by a CritcalSection or atomicCompareandSwap, initialized ot 'Esubmitted'. If the oringinating thread times out its wait on the autoResetEvent, it tries to set the state enum in its serialAPU to 'EtimedOut'. If it succeeds, fine, it returns an error to the caller. Simlarly, in the serial thread, if it finds a serialAPU whose state is EtimedOut, it just removes it from the container. If it finds the serialAPU that matches response data, it tries to change the state to 'EdataRx' and if it succeeds. fires the autoRestEvent.
Another is the annoying OOB data. If that comes in, create a serialAPU, load in the OOB data, set the state to 'EOOBdata' and call some 'OOBevent' with it.
Upvotes: 1
Reputation: 56697
First of all: When you use the SerialPort
class that comes with the framework, the data received event is asynchronous already. When you send something, data is coming in asynchronously.
What I'd try is: queue all requests that need to wait for an answer. In the overall receive handler, check whether the incoming data is the answer for one of the requests. If so, store the reply along with the request information (create some kind of state class for that). All other incoming data is handled normally.
So, how to make the requests wait for an answer? The call that is to send the command and return the reply would create the state object, queue it and also monitor the object to see whether an answer was received. If an answer was received, the call returns the result.
A possible outline could be:
string SendAndWait(string command)
{
StateObject state = new StateObject(command);
state.ReplyReceived = new ManualResetEvent(false);
try
{
SerialPortHandler.Instance.SendRequest(command, state);
state.ReplyReceived.WaitOne();
}
finally
{
state.ReplyReceived.Close();
}
return state.Reply;
}
What's SerialPortHandler
? I'd make this a singleton class which contains an Instance
property to access the singleton instance. This class does all the serial port stuff. It should also contain an event that is raised when "out of band" information comes in (data that is not a reply to a command).
It also contains the SendRequest
method which sends the command to the serial device, stores the state object in an internal list, waits for the command's reply to come in and updates the state object with the reply.
The state object contains a wait handle called ReplyReceived
which is set by the SerialPortHandler
after it has changed the state object's Reply
property. That way you don't need a loop and Thread.Sleep
. Also, instead of calling WaitOne()
you could call WaitOne(timeout)
with timeout
being a number of milliseconds to wait for the reply to come in. This way you could implement some kind of timeout-feature.
This is how it could look in SerialPortHandler
:
void HandlePossibleCommandReply(string reply)
{
StateObject state = FindStateObjectForReply(reply);
if (state != null)
{
state.Reply = reply;
state.ReplyReceived.Set();
m_internalStateList.Remove(state);
}
}
Please note: This is what I'd try to start with. I'm sure this can be very much optimized, but as you see there's not much "multithreading" involved where - only the SendAndWait
method should be called in a way so that multiple clients can issue commands while another client is still waiting for its response.
EDIT
Another note: You're saying that the method should form the basis for a WCF service. This makes things easier, as if you configure the service right, a instance of the service class will be created for every call to the service, so the SendAndWait
method would "live" in its own instance of the service and doesn't even need to be re-entrant at all. In that case, you just need to make sure that the SerialPortHandler
is always active (=> is created and running independently from the actual WCF service), no matter whether there's currently an instance of your service class at all.
EDIT 2
I changed my sample code to not loop and sleep as suggested in the comments.
Upvotes: 2
Reputation: 70324
If you really want to block until the background thread has received your command response, you could look into having the background thread lock an object when you enqueue your command and return that to you. Next, you wait for the lock and continue:
// in main code:
var locker = mySerialManager.Enquee(command);
lock (locker)
{
// this will only be executed, when mySerialManager unlocks the lock
}
// in SerialManager
public object Enqueue(object command)
{
var locker = new Object();
Monitor.Enter(locker);
// NOTE: Monitor.Exit() gets called when command result
// arrives on serial port
EnqueueCommand(command, locker);
return locker;
}
Upvotes: 1
Reputation: 9394
I would advise you to look at the BackgroundWorker-Class
Ther is a Event in this class (RunWorkerCompleted) which is fired when the worker has finished his job.
Upvotes: -1