`SerialPort.Write()` does not block until all data is written.
#96 267 ouverte le 22 déc. 2023
Métriques du dépôt
- Stars
- (17 886 stars)
- Métriques de merge PR
- (Merge moyen 12j 11h) (661 PRs mergées en 30 j)
Description
Description
SerialPort.Write() does not block until all data is written. Also, no TimeoutException is thrown if it fails to write all data by the time the WriteTimeout expires.
One can see at https://github.com/dotnet/runtime/blob/0cf461b302f58c7add3f6dc405873fb2212b513f/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs#L1446-L1492 and https://github.com/dotnet/runtime/blob/0cf461b302f58c7add3f6dc405873fb2212b513f/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs#L1352-L1395 that the number of bytes written is ignored.
Perhaps it should be checked and an error thrown if it is not correct.
Or, perhaps, the Write... functions should be called repeatedly until all data is written.
Or, perhaps, the Write... functions should return the number of bytes actually written, so the user can do something about it.
Reproduction Steps
The following code will reproduce the issue. This is a loopback test, so the port's TX and RX pins must be shorted.
using System.IO.Ports;
int[] BaudRates =
{
9600,
19200,
38400,
57600,
115200,
230400,
460800
};
Parity[] Parities =
{
Parity.None,
Parity.Odd,
Parity.Even
};
string Device = args.Length < 1 ? "COM1" : args[0];
using (var port = new SerialPort(Device))
{
const int timeout = 100;
port.ReadTimeout = timeout;
// 100ms is too short of a time to send 256 bytes to the tx buffer
// on some devices.
port.WriteTimeout = timeout;
const int portBufferSize = 4096;
port.ReadBufferSize = portBufferSize;
port.WriteBufferSize = portBufferSize;
foreach (var baud in BaudRates)
{
foreach (var parity in Parities)
{
Console.WriteLine($"Starting loopback test for {Device} - {baud}, {parity}");
try
{
port.Open();
port.DiscardInBuffer();
port.DiscardOutBuffer();
const int numOfBytes = 2048;
// Fill the txBytes
byte[] txBytes = new byte[numOfBytes];
for (int i = 0; i < numOfBytes; i++)
{
txBytes[i] = (byte)(i & 0xFF);
}
// This succeeds, but it shouldn't. Because the `WriteTimeout` was too short
// only a fraction of the bytes have been written. You won't be able to observe
// this until `port.Read()` is called and the data returned is only a fraction
// of what it should be.
port.Write(txBytes, 0, txBytes.Length);
// This will report 0, but it's a lie. `port.Write()` failed to write all bytes
Console.WriteLine($"{port.BytesToWrite} bytes to write.");
// Give time for data to traverse and fill the rx buffer. This is more than
// enough time for the data to traverse to the rx buffer.
Thread.Sleep(1000);
// Read the data from the port's rx buffer.
byte[] rxBytes = new byte[numOfBytes];
var numBytesRead = port.Read(rxBytes, 0, rxBytes.Length);
if (numBytesRead < numOfBytes)
{
Console.Error.WriteLine($"`port.Read()` was only able to read {numBytesRead} bytes");
}
// Verify all bytes received are correct.
for (int i = 0; i < numOfBytes; i++)
{
if (rxBytes[i] != txBytes[i])
{
Console.Error.WriteLine(string.Format("{0} - {1}, {2}: Expected {3}, received {4}.", Device, baud.ToString(), parity.ToString(), i.ToString(), rxBytes[i].ToString()));
return;
}
}
}
catch (Exception ex)
{
Console.WriteLine($"{Device} - {baud}, {parity}: {ex.Message}");
return;
}
finally
{
port.Close();
}
}
}
}
Expected behavior
SerialPort.Write() should block until all data has been written, or should throw a TimeoutException if all data cannot be written by the time WriteTimeout expires.
Actual behavior
The program fails after the call to port.Read() when it starts verifying that each byte received matches each byte sent. Since the WriteTimeout expired, only some of the data was sent, yet no TimeoutException was thrown. So, when verifying the data, it will only succeed with the few bytes that it received. After which it will fail.
Regression?
Not sure.
Known Workarounds
As long as WriteTimeout in increased to a high enough value to allow enough time for the data to be written to the serial port's TX buffer, everything will work fine. The problem, though, is when it fails, it does so silently, and you'll only know it failed by verifying the data on the receiving end of the serial transmission.
Also, if data is transmitted in small chucks (e.g. 64 bytes at a time) then one can avoid the symptom.
Configuration
No response
Other information
No response