簡體   English   中英

如何使用.NET / C#進行強大的SerialPort編程?

[英]How to do robust SerialPort programming with .NET / C#?

我正在編寫一個Windows服務,用於與串行磁條讀取器和中繼板(訪問控制系統)進行通信。

在另一個程序通過打開與我的服務相同的串行端口“中斷”進程后,我遇到代碼停止工作的問題(我得到IOExceptions)。

部分代碼如下:

public partial class Service : ServiceBase
{
    Thread threadDoorOpener;
    public Service()
    {
        threadDoorOpener = new Thread(DoorOpener);
    }
    public void DoorOpener()
    {
        while (true)
        {
            SerialPort serialPort = new SerialPort();
            Thread.Sleep(1000);
            string[] ports = SerialPort.GetPortNames();
            serialPort.PortName = "COM1";
            serialPort.BaudRate = 9600;
            serialPort.DataBits = 8;
            serialPort.StopBits = StopBits.One;
            serialPort.Parity = Parity.None;
            if (serialPort.IsOpen) serialPort.Close();
            serialPort.Open();
            serialPort.DtrEnable = true;
            Thread.Sleep(1000);
            serialPort.Close();
        }
    }
    public void DoStart()
    {
        threadDoorOpener.Start();
    }
    public void DoStop()
    {
        threadDoorOpener.Abort();
    }
    protected override void OnStart(string[] args)
    {
        DoStart();
    }
    protected override void OnStop()
    {
        DoStop();
    }
}

我的示例程序成功啟動了工作線程,DTR的打開/關閉和升高使我的Mag-stripe讀取器上電(等待1秒),關閉(等待1秒),依此類推。

如果我啟動HyperTerminal並連接到同一個COM端口,HyperTerminal會告訴我該端口當前正在使用中。 如果我在HyperTerminal中反復按ENTER鍵,嘗試重新打開端口,它會在重試幾次后成功。

這會導致我的工作線程中出現IOExceptions,這是預期的。 但是,即使我關閉HyperTerminal,我的工作線程仍然會得到相同的IOException。 唯一的辦法是重啟電腦。

其他程序(不使用.NET庫進行端口訪問)似乎在這一點上正常工作。

關於是什么導致這個的任何想法?

@thomask

是的,Hyperterminal確實在SetCommState的DCB中啟用了fAbortOnError,這解釋了SerialPort對象拋出的大多數IOExceptions。 有些PC /手持設備也有UART,默認情況下會打開錯誤標志中止 - 所以串口的init例程必須清除它(微軟忽略了這一點)。 我最近寫了一篇長篇文章來更詳細地解釋這個問題(如果你有興趣,請看這個 )。

你不能關閉別人與端口的連接,以下代碼永遠不會工作:

if (serialPort.IsOpen) serialPort.Close();

因為您的對象沒有打開端口,所以無法關閉它。

即使發生異常,您也應該關閉並處理串行端口

try
{
   //do serial port stuff
}
finally
{
   if(serialPort != null)
   {
      if(serialPort.IsOpen)
      {
         serialPort.Close();
      }
      serialPort.Dispose();
   }
}

如果您希望該過程可以中斷,那么您應該檢查端口是否打開然后退回一段時間然后再試一次,例如。

while(serialPort.IsOpen)
{
   Thread.Sleep(200);
}

您是否嘗試在應用程序中打開端口,打開/關閉DtrEnable,然后在應用程序關閉時關閉端口? 即:

using (SerialPort serialPort = new SerialPort("COM1", 9600))
{
    serialPort.Open();
    while (true)
    {
        Thread.Sleep(1000);
        serialPort.DtrEnable = true;
        Thread.Sleep(1000);
        serialPort.DtrEnable = false;
    }
    serialPort.Close();
}

我不熟悉DTR語義,所以我不知道這是否有用。

如何做可靠的異步通信

不要使用阻塞方法,內部幫助程序類有一些微妙的錯誤。

將APM與會話狀態類一起使用,其實例管理跨調用共享的緩沖區和緩沖區游標,以及在try...catch中包裝EndRead的回調實現。 在正常操作中, try塊應該做的最后一件事是通過調用BeginRead()來設置下一個重疊的I / O回調。

當出現問題時, catch應異步調用委托給重啟方法。 回調實現應該在catch塊之后立即退出,以便重啟邏輯可以銷毀當前會話(會話狀態幾乎肯定已損壞)並創建新會話。 不能在會話狀態類上實現重新啟動方法,因為這會阻止它破壞和重新創建會話。

當SerialPort對象關閉時(將在應用程序退出時發生),可能存在掛起的I / O操作。 如果是這樣,關閉SerialPort將觸發回調,並且在這些條件下, EndRead將拋出一個與通用comms shitfit無法區分的異常。 您應該在會話狀態中設置一個標志以禁止catch塊中的重新啟動行為。 這將阻止重啟方法干擾自然關機。

可以依賴此體系結構不會意外地保留SerialPort對象。

restart方法管理串行端口對象的關閉和重新打開。 SerialPort對象上調用Close()之后,調用Thread.Sleep(5)讓它有機會放手。 其他東西可以抓住端口,所以准備好在重新打開它時處理它。

我試過改變這樣的工作線程,結果完全相同。 一旦HyperTerminal成功“捕獲端口”(當我的線程正在休眠時),我的服務將無法再次打開端口。

public void DoorOpener()
{
    while (true)
    {
        SerialPort serialPort = new SerialPort();
        Thread.Sleep(1000);
        serialPort.PortName = "COM1";
        serialPort.BaudRate = 9600;
        serialPort.DataBits = 8;
        serialPort.StopBits = StopBits.One;
        serialPort.Parity = Parity.None;
        try
        {
            serialPort.Open();
        }
        catch
        {
        }
        if (serialPort.IsOpen)
        {
            serialPort.DtrEnable = true;
            Thread.Sleep(1000);
            serialPort.Close();
        }
        serialPort.Dispose();
    }
}

此代碼似乎正常工作。 我已經在我的本地機器上在控制台應用程序中測試了它,使用Procomm Plus打開/關閉端口,程序繼續滴答作響。

    using (SerialPort port = new SerialPort("COM1", 9600))
    {
        while (true)
        {
            Thread.Sleep(1000);
            try
            {
                Console.Write("Open...");
                port.Open();
                port.DtrEnable = true;
                Thread.Sleep(1000);
                port.Close();
                Console.WriteLine("Close");
            }
            catch
            {
                Console.WriteLine("Error opening serial port");
            }
            finally
            {
                if (port.IsOpen)
                    port.Close();
            }
        }
    }

我想我已經得出結論,超級終端不能很好地發揮作用。 我運行了以下測試:

  1. 以“控制台模式”啟動我的服務,它開始/關閉設備(我可以通過它的LED告訴)。

  2. 啟動HyperTerminal並連接到端口。 設備保持打開狀態(HyperTerminal引發DTR)我的服務寫入事件日志,無法打開端口

  3. 停止HyperTerminal,我使用任務管理器驗證它已正確關閉

  4. 設備保持關閉狀態(超級終端降低了DTR),我的應用程序繼續寫入事件日志,說它無法打開端口。

  5. 我啟動第三個應用程序(我需要共存的應用程序),並告訴它連接到端口。 我這樣做了。 這里沒有錯誤。

  6. 我停止上面提到的申請。

  7. VOILA,我的服務再次啟動,端口成功打開,LED開啟/關閉。

這個答案很長時間才成為評論......

我相信當你的程序在Thread.Sleep(1000)中並打開超級終端連接時,超級終端可以控制串口。 當您的程序喚醒並嘗試打開串行端口時,將拋出IOException。

重新設計您的方法並嘗試以不同的方式處理端口的打開。

編輯:關於您必須在程序失敗時重新啟動計算機...

這可能是因為你的程序沒有真正關閉,打開你的任務管理員,看看你是否能找到你的程序服務。 在退出應用程序之前,請務必停止所有線程。

有沒有充分的理由讓您的服務“擁有”該端口? 看看內置的UPS服務 - 一旦你告訴它有一個連接到COM1的UPS,你可以親吻那個端口再見。 除非有強大的操作要求來共享端口,否則我建議你這樣做。

暫無
暫無

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

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