2012-04-18 12 views
11

He escrito una aplicación de consola .net 4.0 que habla periódicamente con un módem GSM para obtener una lista de los mensajes SMS recibidos (es un módem USB pero el código se conecta a él a través de un puerto serie controlador y envía comandos AT - por cierto, es un módem Sierra Wireless pero no puedo cambiarlo y tengo el último controlador). Lo que sucede es que después de un período de tiempo (tal vez horas, tal vez días) simplemente deja de funcionar. Aquí es un fragmento de registro ...La clase SerialPort cuelga ocasionalmente Dispose

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' 

Ese es el final del registro - nada más a pesar de que se puede esperar para ver al menos una declaración más, aquí está el código correspondiente:

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; 
} 

Como puede ver, se cuelga en el método Dispose de la clase SerialPort.

Hice algunos Google y llegué a este problema: Serial Port Close Hangs the application de este hilo: serial port hangs whilst closing. Lo consensuado parece ser cerrar el puerto con un hilo diferente, ¿es eso solo para una aplicación de formularios? En mi caso, tengo una aplicación de consola simple, así que no creo que se aplique (solo se ejecuta en un bucle en el hilo principal). Ni siquiera estoy seguro de que este sea realmente el problema (mi sensación es que es más probable que haya un problema con el controlador del puerto serie del módem, pero no sé y quizás estoy siendo injusto con el módem). Por lo que yo veo que tengo tres opciones:

  1. cerrar el puerto en un hilo diferente
  2. Poner en un retardo antes de cerrar el puerto
  3. dejar el puerto abierto para siempre

I realmente no me gusta ninguna de esas soluciones, pero estoy pensando en dejar el puerto abierto y solo ver qué pasa (tengo la sensación de que perderá memoria o algo peor, expondrá algún otro problema con el módem, pero tal vez solo soy pesimista y si ese es el caso, probablemente podría salirse con la suya cerrando cada 24 horas, s ay, y volver a abrirlo de nuevo) así que mi pregunta es ...

¿Hay algún otro problema con este código que pueda estar causando esta falla o hay una solución alternativa a lo que he descrito anteriormente?

+0

¿Qué está pasando en el interior _command.Execute (..)? – PeskyGnat

+0

¿Intentó utilizar la solución alternativa sugerida? Entiendo que todos los ejemplos son Winforms, pero [el artículo de consejos] (http://blogs.msdn.com/b/bclteam/archive/2006/10/10/top-5-serialport-tips-_5b00_kim-hamilton_5d00_. aspx) describe el tema muy claramente. Valdría la pena intentarlo al menos. –

+0

Simplemente está enviando el comando y obteniendo una respuesta (en este caso, envió AT + CPMS = "ME") - sale bien de esa llamada, aunque recibí el mensaje de registro "Desechar el puerto serie para", así que pensé no era demasiado relevante lo que realmente hizo allí? ¿Hay algo que podría estar haciendo que daría lugar a la caída? – kmp

Respuesta

12

SerialPort es algo propenso a un punto muerto. Con mucho, la causa más común es la que encontró, se desencadena al usar Invoke() en un controlador de eventos DataReceived. Claramente no es tu caso aquí.

Estos bloqueos están relacionados con un hilo de trabajo que SerialPort comienza detrás de la cortina. Ese hilo ayuda a detectar eventos asincrónicos en el puerto, el winapi nativo subyacente es WaitCommEvent(). Ese trabajador hace que los eventos DataReceived, PinChanged y ErrorReceived funcionen. Observe cómo haga use ErrorReceived.

El método Dispose() hace lo mismo que el método Close(), señala el hilo de trabajo para salir. Sin embargo, la falla es que no espera para que salga el hilo. Esa es una receta para problemas, el tipo que se documenta explícitamente en el artículo de MSDN para SerialPort.Close() en la sección Comentarios:

La mejor práctica para cualquier aplicación es esperar a que una cierta cantidad de tiempo después de llamar al Cierre el método antes de intentar llamar al método Open, ya que es posible que el puerto no se cierre al instante.

Cuál es, francamente, la peor práctica posible para el consejo de "mejores prácticas" ya que no especifica en absoluto cuánto tiempo se espera que espere. Por una buena razón, no hay un valor seguro garantizado. Esperar un segundo o dos debería ser 99.9% bueno. El modo de falla 0.1% ocurre cuando la máquina está muy cargada y la cadena de trabajo simplemente no recibe suficientes ciclos para detectar la condición de cierre en el tiempo. Absolutamente undebuggable por supuesto.

Elimine este problema, solo abra un puerto serie al principio de su programa y ciérrelo al salir. En lugar de causar problemas, esto también garantiza que no pierda el acceso al puerto al azar cuando otro programa salta y le roba el puerto. Y tenga en cuenta que cerrar el puerto ya no es necesario, Windows se ocupará de eso si no lo hace.

+0

Gracias, buena información. Iré con la opción 3 y veré qué pasa. Por cierto, ¿qué pasa si simplemente no me molesté con el evento ErrorReceived (significaría que este hilo de fondo no se iniciará en absoluto)? Y está colgando en el Dispose, no en el Open, así que supongo que la demora no se aplicaría, ¿verdad? – kmp

1

Si está utilizando el evento DataRecibved o cualquier otro evento de su puerto serie, debe quitar su controlador de eventos antes de deshacerse del puerto serie.

mySerial.DataReceived -= DataReceivedHandler; 
mySerial.Dispose(); 

El bloqueo se produce porque tiene un evento que dispara sobre un objeto eliminado ... lo que obviamente es un error.

Sin embargo, en su caso lo ha hecho ... y el bloqueo se produce porque el puerto no se ha cerrado. posiblemente un thread.sleep podría permitir que el puerto se "estabilice" antes de intentar reabrirlo. Probablemente también sea específico del hardware ... por lo que no hay una mejor práctica.

La misma que para un control de formularios: How to remove all event handlers from a control

Cuestiones relacionadas