2009-01-14 56 views
24

我正在寫一個Windows服務與串行磁條閱讀器和中繼板(訪問控制系統)通信。如何使用.NET/C#進行健壯的SerialPort編程?

在另一個程序通過打開與我的服務相同的串行端口「中斷」進程後,我遇到代碼停止工作(我得到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的開/關和提升使我的磁條閱讀器上電(等待1秒),關閉(等待1秒)等。

如果我啓動超級終端並連接到同一個COM端口,HyperTerminal告訴我該端口當前正在使用。如果我在超級終端中反覆按ENTER鍵,嘗試重新打開 端口,它將在幾次重試後成功。

這會導致在我的工作線程中產生IOExceptions,這是預期的。但是,即使關閉了超級終端,我仍然在我的工作線程中得到相同的IOException。唯一的辦法是重新啓動電腦。

其他程序(不使用.NET庫進行端口訪問)似乎此時正常工作。

任何想法是什麼造成這種情況?

回答

23

@thomask

是的,超級終端將在事實上使fAbortOnError在SetCommState的DCB,這也解釋了大多數通過一個串口對象拋出的IOExceptions的。一些PC /手持設備也有默認打開錯誤標誌中止的UART--因此,串行端口的初始化程序清除它是非常必要的(微軟忽略了這一點)。我最近寫了一篇很長的文章來更詳細地解釋這一點(如果您有興趣,請參閱this)。

4

您不能關閉的端口別人的連接,下面的代碼不會有任何效果:

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); 
} 
+0

不只是關閉它,也配置它! SerialPort實現IDisposable,所以你可能想使用(SerialPort serialPort = new SerialPort){/ * Daniel's code * /} – 2009-01-14 01:45:17

+0

好點將編輯我的帖子 – trampster 2009-01-14 01:46:51

+0

但是,這並不能解釋爲什麼我的應用程序拒絕打開端口被其他應用程序中斷後。還是呢? – 2009-01-14 01:52:05

2

您是否嘗試過離開口開在你的應用程序,並恰好在開啓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的語義,所以我不知道這是否會工作。

1

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

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(); 
    } 
} 
1

此代碼似乎正常工作。我已經在控制檯應用程序的本地機器上測試過它,使用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(); 
      } 
     } 
    } 
0

此答案需要很長時間才能成爲評論...

我相信當你的程序在一個Thread.Sleep(1000)中,並且你打開你的超級終端連接時,HyperTerminal控制這個串口。當你的程序醒來並試圖打開串口時,拋出一個IOException。

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

編輯: 關於你有當你的程序無法重新啓動計算機......

這可能是因爲你的程序isn't真的關閉了,打開任務管理器,看看是否能找到您的節目服務。確保在退出應用程序之前停止所有線程。

0

是否有充分的理由讓您的服務免於「擁有」端口?看看內置的UPS服務 - 一旦你告訴它有一個連接到COM1的UPS,你可以親吻那個端口。我建議你也這樣做,除非有強大的操作要求共享端口。

1

我想我得出的結論是超級終端打不好。我已經運行下面的測試:

  1. 在「控制檯模式」開始我的服務,它啓動開關設備的開/關(我可以告訴通過它的LED)。

  2. 啓動超級終端並連接到端口。 設備停留在(超級引起DTR) 我的服務寫入事件日誌,它不能打開端口

  3. 停止超級終端,我驗證它是否使用任務管理器

  4. 器件將處於正常關閉(超級終端降低了DTR),我的應用程序不斷寫入事件日誌,說它無法打開端口。

  5. 我開始第三個應用程序(我需要與之共存的應用程序),並告訴它連接到端口。我這樣做。這裏沒有錯誤。

  6. 我停止上述應用程序。

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

2

如何做好可靠的異步通訊科

不要使用攔截方法,內部輔助類有一些微妙的錯誤。

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

當事情發生錯誤時,catch應異步調用委託給重新啓動方法。回調實現應在catch塊之後立即退出,以便重新啓動邏輯可以銷燬當前會話(會話狀態幾乎肯定已損壞)並創建新會話。重新啓動方法必須在會話狀態類上實現而不是,因爲這會阻止它銷燬並重新創建會話。

當SerialPort對象關閉時(當應用程序退出時會發生這種情況),可能會有一個掛起的I/O操作。如果是這樣,關閉串口將觸發回調,在這種情況下,EndRead會拋出一個與普通通用shitfit無法區分的異常。您應該在會話狀態中設置一個標誌以禁止catch塊中的重新啓動行爲。這將會阻止您的重啓方法干擾自然關機。

這種體系結構可以依賴於不會意外地持有Seri​​alPort對象。

重啓方法管理關閉和重新打開串口對象。撥打SerialPort對象後,致電Close(),請致電Thread.Sleep(5),讓它有機會放手。有可能有其他東西抓住端口,所以準備在重新打開它時處理這個問題。