我已经编写了一个.net 4.0控制台应用程序,该应用程序定期与GSM调制解调器通信以获取收到的SMS消息列表(它是USB调制解调器,但代码通过串行端口驱动程序连接到它并发送AT命令 - 顺便说一句,这是一个Sierra无线调制解调器,但我不能改变它,我有最新的驱动程序)。会发生什么事情,经过一段时间(可能是几小时,也许几天),它就会停止工作。这是一个日志片段......
2012-04-17 23:07:31 DEBUG Modem Check (108) - Executing AT command 'AT+CPMS="ME"'...
2012-04-17 23:07:31 DEBUG Modem Check (108) - Finished executing 'AT+CPMS="ME"'
2012-04-17 23:07:31 DEBUG Modem Check (108) - Detaching event handlers for 'COM13'
2012-04-17 23:07:31 DEBUG Modem Check (108) - Disposing the SerialPort for 'COM13'
这就是日志的结束 - 即使我希望至少再看一个语句,也就是相关代码:
internal T Execute()
{
var modemPort = new SerialPort();
T ret;
try
{
modemPort.ErrorReceived += ModemPortErrorReceived;
modemPort.PortName = _descriptor.PortName;
modemPort.Handshake = Handshake.None;
modemPort.DataBits = 8;
modemPort.StopBits = StopBits.One;
modemPort.Parity = Parity.None;
modemPort.ReadTimeout = ReadTimeout;
modemPort.WriteTimeout = WriteTimeout;
modemPort.NewLine = "\r\n";
modemPort.BaudRate = _descriptor.Baud;
if (!modemPort.IsOpen)
{
modemPort.Open();
}
ret = _command.Execute(modemPort, _logger);
_logger.Debug("Detaching event handlers for '{0}'",
_descriptor.PortName);
modemPort.ErrorReceived -= ModemPortErrorReceived;
_logger.Debug("Disposing the SerialPort for '{0}'",
_descriptor.PortName);
}
catch (IOException ex)
{
_logger.Error(ex.Message);
throw new CommandException(
string.Format(CultureInfo.CurrentCulture,
ModemWrapperStrings.COMMAND_ERROR,
ex.Message),
ex);
}
catch (UnauthorizedAccessException ex)
{
_logger.Error(ex.Message);
throw new CommandException(
string.Format(CultureInfo.CurrentCulture,
ModemWrapperStrings.COMMAND_ERROR,
ex.Message),
ex);
}
finally
{
modemPort.Dispose();
_logger.Debug("Modem on port '{0}' disposed",
_descriptor.PortName);
}
return ret;
}
正如您所看到的,它挂起在SerialPort类的Dispose方法上。
我做了一些谷歌搜索,我来到这个问题:Serial Port Close Hangs the application来自这个帖子:serial port hangs whilst closing。双方同意似乎是在一个不同的线程关闭端口,但这只是一个表单应用程序?在我的情况下,我有一个简单的控制台应用程序,所以我不认为它适用(它只是在主线程中的循环中运行)。我甚至不确定它实际上是这个问题(我的感觉是,调制解调器的串口驱动程序更可能存在问题,但我不知道,也许我对调制解调器不公平)。据我所知,我有三个选择:
我真的不喜欢这些变通办法,但是我想把端口打开,只是看看会发生什么(我觉得它会泄漏内存或更糟,暴露调制解调器的其他问题但也许我我只是悲观,如果是这样的话,我可能会每隔24小时关闭它,比如说再重新打开它,所以我的问题是......
此代码是否存在替代问题,可能会导致这种情况发生,或者是否存在我上面概述的替代解决方法?
答案 0 :(得分:14)
SerialPort有点容易死锁。到目前为止,最常见的原因是您找到的原因,它是通过在DataReceived事件处理程序中使用Invoke()来触发的。显然不是你的情况。
这些死锁与SerialPort幕后启动的工作线程有关。该线程有助于检测端口上的异步事件,底层的本机winapi是WaitCommEvent()。该工作程序使DataReceived,PinChanged和ErrorReceived事件起作用。请注意 如何使用ErrorReceived。
Dispose()方法与Close()方法的作用相同,它表示工作线程退出。然而,缺点是它没有等待线程退出。这是一个麻烦的方法,在备注部分的MSDN文章中明确记录了SerialPort.Close():
坦率地说,这是"最佳实践"最糟糕的做法。建议,因为它根本没有说明你应该等待多长时间。有充分理由,没有保证的安全价值。等待一两秒应该是99.9%好。当机器负载很重并且工作线程没有足够的周期来及时检测到关闭状态时,会发生0.1%故障模式。当然完全不可取。任何应用程序的最佳实践是在尝试调用Open方法之前等待一段时间后再调用Close方法,因为端口可能不会立即关闭。
解决这个问题,只需在程序启动时打开一个串口,并在退出时将其关闭。除了线程故障之外,这还可以确保当另一个程序跳入并将端口远离您时,您不会随机丢失对该端口的访问权限。请注意,关闭端口不再是实际需要的,如果你不这样做,Windows将会处理它。
答案 1 :(得分:3)
如果您正在使用DataRecieved事件或串行端口对象中的任何其他事件,则应在处置串行端口之前从中删除事件处理程序。
mySerial.DataReceived -= DataReceivedHandler;
mySerial.Dispose();
发生挂起是因为您在已处置的对象上触发事件...这显然是一个错误。
但是,在您的情况下,您已经完成了...并且由于端口尚未关闭而导致挂起。可能一个thread.sleep可能允许端口在尝试重新打开之前“解决”。它可能也是特定的硬件......这就是没有最佳实践的原因。
与Forms控件相同: How to remove all event handlers from a control