I have a peculiar problem.
A custom protocol which I need to use to communicate with a serial device works as follows, I'm using standard ASCII abreviations.
If the message was a request expecting an answer, the device sends back data. Because the way SerialPort.DataReceived
works I have to piece the received message back together manually.
It goes a bit like this:
This is basically the way these two communicate, barring lost packets and NAKs and those things, but I don't want to make it too complicated.
My problem is: How do I wrap either the sending of a complete message or receiving of a complete message into an atomic operation? What I don't want is my program sending a new message while I'm in the middle of gluing a received message back together.
I have tried using Monitor.Enter()
and Monitor.Exit()
but the received event is called on a different thread, so no dice.
I have also tried using a Semaphore
with only 1 resource, calling semaphore.WaitOne()
at the start of either sending or receiving and calling semaphore.Release()
after I have sent the EOT and after I have received an EOT from the device. This also doesn't work quite well.
Is there any way to do this better?
I would use a lock:
http://msdn.microsoft.com/en-gb/library/c5kehkcz(v=vs.71).aspx
You can setup a global object say serialLock and use this to make sure that when you are glueing pieces together any send threads have to wait:
In the received
lock(serialLock)
{
//Glue data
}
In all send:
lock(serialLock)
{
// send data
}
This will sort any problems with different threads etc.
I would also make sure you have received a full message in the glue data section before releasing the lock. Maybe to make this cleared would be you will need to update a thread-safe data structure in the DataReceived event and in the glue data section you will need to stay in this section checking the data structure for a full message before releasing your lock.
Faced a similar problem when I wrote code to communicate with a GSM Modem over serial port.
I was able to develop a reasonably good solution by making use of AutoResetEvent
class in the System.Threading
namespace. Basically, I made my Send method to wait for the ACK signal.
Here is a skeleton code.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.IO.Ports;
namespace Sample
{
public class SocketWrapper
{
#region Static Variables
private static AutoResetEvent _SendWaitHandle = new AutoResetEvent(false);
#endregion
#region Member Variables
private object _SendLockToken = new object();
#endregion
#region Public Methods
public void Write(byte[] data)
{
Monitor.Enter(_SendLockToken);
try
{
// Reset Handle
_SendWaitHandle.Reset();
// Send Data
// Your Logic
// Wait for ACK
if (_SendWaitHandle.WaitOne(1000)) // Will wait for 1000 miliseconds
{
// ACK Received
// Send EOT
}
else
{
// Timeout Occurred
// Your Logic To Handle Timeout
}
}
catch (Exception)
{
throw;
}
finally
{
Monitor.Exit(_SendLockToken);
}
}
#endregion
#region Private Methods
private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
// When ACK is received call SET
_SendWaitHandle.Set();
}
#endregion
}
}
Note:
In DataReceived method, make sure that you don't call the Write method directly/indirectly, as this could result in a DeadLock. Always start processing the received data on a different thread using BackgroundWorker
or by the using the TPL.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With