[英]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();
}
}
}
我想我已經得出結論,超級終端不能很好地發揮作用。 我運行了以下測試:
以“控制台模式”啟動我的服務,它開始/關閉設備(我可以通過它的LED告訴)。
啟動HyperTerminal並連接到端口。 設備保持打開狀態(HyperTerminal引發DTR)我的服務寫入事件日志,無法打開端口
停止HyperTerminal,我使用任務管理器驗證它已正確關閉
設備保持關閉狀態(超級終端降低了DTR),我的應用程序繼續寫入事件日志,說它無法打開端口。
我啟動第三個應用程序(我需要共存的應用程序),並告訴它連接到端口。 我這樣做了。 這里沒有錯誤。
我停止上面提到的申請。
VOILA,我的服務再次啟動,端口成功打開,LED開啟/關閉。
這個答案很長時間才成為評論......
我相信當你的程序在Thread.Sleep(1000)中並打開超級終端連接時,超級終端可以控制串口。 當您的程序喚醒並嘗試打開串行端口時,將拋出IOException。
重新設計您的方法並嘗試以不同的方式處理端口的打開。
編輯:關於您必須在程序失敗時重新啟動計算機...
這可能是因為你的程序沒有真正關閉,打開你的任務管理員,看看你是否能找到你的程序服務。 在退出應用程序之前,請務必停止所有線程。
有沒有充分的理由讓您的服務“擁有”該端口? 看看內置的UPS服務 - 一旦你告訴它有一個連接到COM1的UPS,你可以親吻那個端口再見。 除非有強大的操作要求來共享端口,否則我建議你這樣做。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.