簡體   English   中英

SerialPort.Read(....) 不尊重 ReadTimeOut

[英]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% 的時間內重現它。 但遺憾的是,在生產中的試點測試期間,問題仍然繼續發生。

支付終端的代碼是幾年前編寫的,我將它移植到另一個應用程序中。 在移植期間,我重構了一些代碼,但保留了原始功能。 與終端通信時,代碼將:

  1. 從線程池中觸發另一個線程
  2. 向設備發送消息
  3. 從串行端口讀取,直到收到響應或發生超時。

同時,主線程有一個包含 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM