[英]SerialPort.Read(....) does not respect ReadTimeOut
在一些與支付終端通信的遺留代碼中發現了一個錯誤。
就在開始新的付款之前,代碼嘗試清除 SerialPort 的內部讀取緩沖區。
我將代碼精簡到最低限度。 它使用 .NET SerialPort 類型。 讀取超時設置為 50 毫秒。 然后它讀取 512 個字節並繼續這樣做,直到不再讀取字節或直到拋出 TimeoutException。
我們添加了一堆日志,它顯示調用第一個 Read(...) 方法有時需要 10 - 15 分鍾,即使超時 50 毫秒。 然后拋出 TimeoutException 並且應用程序繼續。 但是在 Read(...) 期間,應用程序掛起。
這並不總是發生,Windows 2000 機器由於某種原因似乎更容易出現此錯誤。
public class Terminal
{
private SerialPort _serialPort = new SerialPort();
public void ClearReadBuffer()
{
try
{
_serialPort.ReadTimeout = 50;
int length;
do
{
var buffer = new byte[512];
length = _serialPort.Read(buffer, 0, 512);
} while (length > 0);
}
catch (TimeoutException) {}
}
}
任何幫助表示贊賞。
PS:大多數錯誤報告來自 W2K 機器,其中設備連接到 EdgePort,它模擬了一堆虛擬 COM 端口。 它的驅動程序創建了一堆(8 個左右)本地 COM 端口。
但是我們也有來自 Windows 7 的報告。如果我們將設備直接連接到 PC(沒有 EdgePort),我們也可以重現該問題。 然而並不那么頻繁,當它發生時延遲不是 10 分鍾,而是更像是 1 - 2 分鍾。
更新:嘗試了很多方法來解決這個問題。 很難復制,但在現場經常發生,因為它分布在數千台 PC 上。 實際上用另一個開源版本替換了 .NET 2.0 SerialPort 類型。 在一台 PC 上工作沒有問題,我們實際上可以在 60 - 70% 的時間內重現它。 但遺憾的是,在生產中的試點測試期間,問題仍然繼續發生。
支付終端的代碼是幾年前編寫的,我將它移植到另一個應用程序中。 在移植期間,我重構了一些代碼,但保留了原始功能。 與終端通信時,代碼將:
同時,主線程有一個包含 Thread.Sleep(50) 和 Application.DoEvents() 調用的 while 循環(糟糕!)。 我重構了整個“等待循環”,並使用了 WaitHandle (AutoResetEvent / ManualResetEvent)。 我只是等到這個句柄被設置。 工作沒有問題,但在某些 PC 上,所有串行端口通信都會凍結幾分鍾,直到某些事情觸發它。 重新啟用 Application.DoEvents() 的工作方式,問題就消失了。
不幸的是,它仍然在那里,這對我來說是一個謎,為什么這里需要它以及為什么它會導致如此嚴重的副作用。 該應用程序支持 5 種其他類型的串口設備。 與這些設備通信從來不需要這樣的事情。
可能在端口的“要讀取的字節”上添加測試可能有助於避免編碼錯誤的驅動程序:
length = (_serialPort.BytesToRead > 0) ? _serialPort.Read(buffer, 0, 512) : 0;
更好的是,使用
_serialPort.DiscardInBuffer();
代替!
解決此問題的最簡單方法是使用ReadTimeOut
本身,但獨立於接收到的內容執行讀取。
閱讀 Microsoft 的ReadTimeOut
文檔:
如果ReadTimeout設置為
TimeSpan.FromMilliseconds(ulong.MaxValue)
(請參閱TimeSpan ),則讀取請求會立即使用已接收的字節完成,即使尚未接收到任何字節。
換句話說,這樣它會立即讀取串行緩沖區中的任何內容。
你可以像這樣實現它:
SerialDevice SerialDeviceComm;
Task.Run(async () => { SerialDeviceComm = await SerialDevice.FromIdAsync(_serialConnectionInfo[_indexPort].PortID); }).Wait();
SerialDeviceComm.BaudRate = _baudRate;
SerialDeviceComm.Parity = _parity;
SerialDeviceComm.StopBits = _stopBitCount;
SerialDeviceComm.DataBits = _dataBit;
SerialDeviceComm.Handshake = _serialHandshake;
// This will make that regardless of receiving bytes or not it will read and continue.
SerialDeviceComm.ReadTimeout = TimeSpan.FromMilliseconds(uint.MaxValue); // Secret is here
dataReader = new DataReader(SerialDeviceComm.InputStream)
{
InputStreamOptions = InputStreamOptions.Partial
};
string _tempData = "";
Stopwatch sw = new Stopwatch();
sw.Reset();
sw.Start();
while (sw.ElapsedMilliseconds < timeOut)
{
uint receivedStringSize = 0;
Task.Delay(50).Wait(); // Time to take some bytes
dataReader.InputStreamOptions = InputStreamOptions.Partial;
Task.Run(async () => { receivedStringSize = await dataReader.LoadAsync(200); }).Wait();
_tempData += dataReader.ReadString(receivedStringSize);
}
你可以在我的 git: GitHub with example 中看到一個示例代碼
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.