簡體   English   中英

.NET SerialPort DataReceived 事件線程干擾主線程

[英].NET SerialPort DataReceived event thread interference with main thread

我正在使用 C# 中的 SerialPort 類編寫串行通信程序,以與通過 RS232 電纜連接的脫衣機進行交互。 當我將命令發送到機器時,它會根據命令以一些字節進行響應。 就像我發送“\D”命令時,我希望下載 180 字節的機器程序數據作為一個連續的字符串。 根據機器手冊,建議發送一個無法識別的字符(如逗號 (,) 字符)作為最佳做法,以確保在發送循環中的第一個命令之前機器已初始化。 我的串口通信代碼如下:

public class SerialHelper
{
    SerialPort commPort = null;
    string currentReceived = string.Empty;
    string receivedStr = string.Empty;
    private bool CommInitialized()
    {
        try
        {
            commPort = new SerialPort();
            commPort.PortName = "COM1";
            if (!commPort.IsOpen)
                commPort.Open();
            commPort.BaudRate = 9600;
            commPort.Parity = System.IO.Ports.Parity.None;
            commPort.StopBits = StopBits.One;
            commPort.DataBits = 8;
            commPort.RtsEnable = true;
            commPort.DtrEnable = true;

            commPort.DataReceived += new SerialDataReceivedEventHandler(commPort_DataReceived);
            return true;
        }
        catch (Exception ex)
        {
            return false;
        }
    }

    void commPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        SerialPort currentPort = (SerialPort)sender;
        currentReceived = currentPort.ReadExisting();
        receivedStr += currentReceived;
    }

    internal int CommIO(string outString, int outLen, ref string inBuffer, int inLen)
    {
        receivedStr = string.Empty;
        inBuffer = string.Empty;
        if (CommInitialized())
        {
            commPort.Write(outString);
        }

        System.Threading.Thread.Sleep(1500);

        int i = 0;
        while ((receivedStr.Length < inLen) && i < 10)
        {
            System.Threading.Thread.Sleep(500);
            i += 1;
        }

        if (!string.IsNullOrEmpty(receivedStr))
        {
            inBuffer = receivedStr;
        }
        commPort.Close();

        return inBuffer.Length;

    }
}

我從 Windows 窗體調用此代碼,如下所示:

len = SerialHelperObj.CommIO(",",1,ref inBuffer, 4)
len = SerialHelperObj.CommIO(",",1,ref inBuffer, 4)
If(inBuffer == "!?*O")
{
   len = SerialHelperObj.CommIO("\D",2,ref inBuffer, 180)
}

串行端口的有效返回值如下所示:\D00000010000000000010 550 3250 0000256000 等等...

我得到這樣的東西:\D00000010D,, 000 550 D,,等等...

當我發送命令時,我覺得我的通訊呼叫受到了干擾。 但我試圖確保逗號命令的結果然后啟動實際命令。 但是接收線程正在插入來自前一個通信周期的字節。

任何人都可以闡明這一點......? 為了完成這項工作,我掉了很多頭發。 我不確定我哪里做錯了

串行端口不是線程安全資源,使用它們的包裝器或您用來保存結果的字符串也不是。 發生的情況是 DataReceived 事件處理程序被多次觸發,並且您最終會同時執行多個處理程序。 嘗試向您的事件處理程序添加一個鎖塊:

...

Object lockingObj = new Object();

...

void commPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    lock(lockingObj)
    {
        SerialPort currentPort = (SerialPort)sender;
        currentReceived = currentPort.ReadExisting();
        receivedStr += currentReceived;
    }
}

lock 語句可防止多個線程同時執行塊中的代碼,因此 receivedStr 將在寫入另一個 ReadExisting 之前獲取一個 ReadExisting 的全部內容。

請注意,這並不能保證執行順序; 先進者獲勝。 所以,假設線程 1 先到達這里,然后線程 2 在 250 毫秒后到達,然后線程 3 在 250 毫秒后到達。 線程 1 可能會先進入,執行需要一段時間的讀取,然后退出。 在那段時間,線程 2 和 3 進入函數並被鎖定語句阻塞,等待線程 1 釋放它的鎖。 一旦發生這種情況,就取決於內核首先調度哪個線程,並且可能是線程 3,具體取決於許多操作系統和硬件因素。

為什么你每次都重新初始化你的端口? 我想一個就夠了。

我發現您的代碼存在許多問題。

  1. 永遠不要忽略異常。 返回false是忽略異常。 你不知道當你這樣做時發生了哪個異常。
  2. 特別是,您的commPort對象可能從未被實例化。 它可能處於任何狀態,但您將忽略異常並繼續訪問可能無效的對象。
  3. DataReceived事件可以在端口打開時隨時發生。 它可能會也可能不會在同一個線程上引發。 您的代碼可能正在從事件處理程序下關閉該端口,這會引發您的代碼未捕獲的異常。
  4. 這在這里可能無關緊要,但您知道字符串連接在 .NET 中是一項相對昂貴的操作嗎? 字符串是不可變的。 receivedStr += currentReceived不會附加到receivedStr - 它會創建一個新的字符串對象來保存這兩個部分。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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