Reputation: 327
I have a device that communicates through RS485 and I need to make a loopback in C# that receives my data and send them back. First byte contains information about the length of the coming data. Sometimes, it occurs that C# fire an event in several parts. I would need that I received them only in one part to I can send back in one go.
var buffer = new byte[1000];
port = new SerialPort(ConfigParams.PORT, 115200, Parity.None, 8, StopBits.One);
port.Handshake = Handshake.None;
port.RtsEnable = true;
port.DtrEnable = true;
port.DataReceived += (sender, e) =>
{
var length = port.ReadByte();
Thread.Sleep(10);
var bytes = port.Read(buffer, 1, length);
buffer[0] = (byte)length;
port.Write(buffer, 0, length + 1);
mainForm.Log("Port (" + (length + 1).ToString() +"): " + Encoding.UTF8.GetString(buffer, 0, length + 1)+"\n");
mainForm.Log("Data was sended.\n");
};
In first step, I read first byte to get number of coming bytes. Then, I read the rest of the bytes. If I don't insert there the line Thread.Sleep(10), Event DataReceived will fire in several parts sometimes. With Thread.Sleep(10) it works correctly every time.
Do you know why it happens? I would expect that if I say I want to read 40 bytes, SerialPort will try to receive all those 40 bytes or occur an exception. I tried to change the property ReadTimeout but no change.
Upvotes: 2
Views: 4345
Reputation: 109567
The problem here is that when you call SerialPort.Read()
it will wait for at least one byte in the buffer and then it will return all the bytes that it has, up to a maximum of the specified length.
That means that it is possible for it to return less than the amount asked for.
To circumvent this issue, you can write code similar to this (the important bit is in the private blockingRead()
method):
/// <summary>
/// Attempts to read <paramref name="count"/> bytes into <paramref name="buffer"/> starting at offset <paramref name="offset"/>.
/// If any individual port read times out, a <see cref="TimeoutException"/> will be thrown.
/// </summary>
/// <param name="buffer">The byte array to write the input to. </param>
/// <param name="offset">The offset in buffer at which to write the bytes. </param>
/// <param name="count">The number of bytes to read from the port.</param>
/// <param name="timeoutMilliseconds">
/// The timeout for each individual port read (several port reads may be issued to fulfil the total number of bytes required).
/// If this is -2 (the default) the current <see cref="ReadTimeout"/> value is used.
/// If this is -1 or <see cref="SerialPort.InfiniteTimeout"/>, an infinite timeout is used.
/// </param>
/// <exception cref="TimeoutException">Thrown if any individual port read times out.</exception>
public void BlockingRead(byte[] buffer, int offset, int count, int timeoutMilliseconds = SerialComPortTimeout.UseDefault)
{
if (timeoutMilliseconds < SerialComPortTimeout.UseDefault)
throw new ArgumentOutOfRangeException(nameof(timeoutMilliseconds),timeoutMilliseconds, $"{nameof(timeoutMilliseconds)} cannot be less than {SerialComPortTimeout.UseDefault}." );
int timeoutToRestore = setTimeoutAndReturnOriginal(timeoutMilliseconds);
try
{
blockingRead(buffer, offset, count);
}
finally
{
if (timeoutToRestore != SerialComPortTimeout.UseDefault)
this.ReadTimeout = timeoutToRestore;
}
}
private void blockingRead(byte[] buffer, int offset, int count)
{
while (count > 0)
{
// SerialPort.Read() blocks until at least one byte has been read, or SerialPort.ReadTimeout milliseconds
// have elapsed. If a timeout occurs a TimeoutException will be thrown.
// Because SerialPort.Read() blocks until some data is available this is not a busy loop,
// and we do NOT need to issue any calls to Thread.Sleep().
int bytesRead = _serialPort.Read(buffer, offset, count);
offset += bytesRead;
count -= bytesRead;
}
}
private int setTimeoutAndReturnOriginal(int timeoutMilliseconds)
{
int originalTimeout = this.ReadTimeout;
if ((timeoutMilliseconds != SerialComPortTimeout.UseDefault) && (originalTimeout != timeoutMilliseconds))
{
this.ReadTimeout = timeoutMilliseconds;
return originalTimeout;
}
return SerialComPortTimeout.UseDefault;
}
Be aware that the .Net implementation of SerialPort
is flakey - see this article for details.
Upvotes: 2