2009-01-14 18 views
24

Estoy escribiendo un Servicio de Windows para la comunicación con un lector Serial Mag-stripe y una placa de relés (sistema de control de acceso).¿Cómo hacer una programación sólida de SerialPort con .NET/C#?

Me encuentro con problemas donde el código deja de funcionar (obtengo IOExceptions) después de que otro programa ha "interrumpido" el proceso abriendo el mismo puerto serie que mi servicio.

parte del código es el siguiente:

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

Mi programa de ejemplo comienza con éxito el trabajo de rosca, y la apertura/cierre y elevación de DTR hace que mi lector de banda magnética para encender (espere 1 segundo), apague (espere 1 segundo) y así sucesivamente.

Si abro HyperTerminal y me conecto al mismo puerto COM, HyperTerminal me dice que el puerto está actualmente en uso. Si presiono repetidamente ENTRAR en HyperTerminal, para intentar reabrir el puerto tendrá éxito después de unos pocos intentos.

Esto tiene el efecto de causar IOExceptions en mi hilo de trabajo, que se espera. Sin embargo, incluso si cierro HyperTerminal, todavía obtengo la misma IOException en mi hilo de trabajo. La única cura es en realidad reiniciar la computadora.

Parece que otros programas (que no utilizan las bibliotecas .NET para acceso a puertos) funcionan normalmente en este punto.

¿Alguna idea de qué está causando esto?

Respuesta

23

@thomask

Sí, Hiperterminal de hecho permiten fAbortOnError en DCB de SetCommState, lo que explica la mayor parte de los IOExceptions lanzadas por el objeto SerialPort. Algunas PC/computadoras de mano también tienen UART que tienen activada la bandera de abortar por error, por lo que es imperativo que la rutina de inicio de un puerto serie la borre (lo cual Microsoft omitió). Recientemente escribí un artículo extenso para explicar esto en mayor detalle (vea this si le interesa).

+0

@Zach Saw: Gran artículo que escribió , ¿podría desafiarlo a que prepare una muestra de trabajo para borrar fAbortOnError? :) –

+0

@thomask: Claro. Escribiré algo rápidamente y lo publicaré como un artículo de seguimiento más tarde. –

+4

Aquí está el código. http://zachsaw.blogspot.com/2010/07/serialport-ioexception-workaround-in-c.html –

4

No se puede cerrar la conexión a alguien vigilara a un puerto, el siguiente código no funcionará:

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

Debido a que su objeto no se abrió el puerto que no puede cerrarla.

También se debe cerrar y disponer el puerto serie, incluso después de excepciones se producen

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

Si desea que el proceso sea interrumpible entonces usted debe comprobar si el puerto está abierto y luego de vuelta por un período y luego intenta de nuevo, algo así como

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

¡No solo lo cierre, deséchelo también! SerialPort implementa IDisposable, por lo que es posible que desee utilizar con (SerialPort serialPort = new SerialPort) {/ * Código de Daniel * /} –

+0

Buen punto editará mi publicación – trampster

+0

Pero aún así, esto no explica por qué mi aplicación se niega a abrir la puerto después de que ha sido interrumpido por otra aplicación. O lo hace? –

2

Ha intentado dejar el puerto abierto en la aplicación, y simplemente girando DTREnable de encendido/apagado, y luego cerrar el puerto cuando la aplicación se cierra? es decir:

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

No estoy familiarizado con DTR semántica, así que no sé si esto funcionaría.

+0

Este dispositivo necesita DTR = 1 para "activar", por lo que básicamente el código enciende/apaga el dispositivo cada segundo. –

+0

¿Mi código hace lo que necesita? Parece que es lo mismo que lo que hace la pregunta, pero sin renunciar al recurso. – FryGuy

+0

Realmente necesito que el puerto esté disponible para otra aplicación entre los ciclos. –

1

He intentado cambiar el hilo de trabajo de esta manera, con exactamente el mismo resultado. Una vez que HyperTerminal logra "capturar el puerto" (mientras mi hilo está durmiendo), mi servicio no podrá volver a abrir el puerto.

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

Este código parece funcionar correctamente. Lo probé en una máquina local en una aplicación de consola, utilicé Procomm Plus para abrir/cerrar el puerto y el programa sigue funcionando.

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

Sí, ese código funciona. Aunque cuando se interrumpe con HyperTerminal, falla miserablemente, con el mensaje de error repetitivo en el bloque catch. Esto me lleva a creer que hay algo mal con el estado en el que HyperTerminal deja el puerto. –

+0

Con respecto al código en el bloque finally. ¿No podría la sentencia port.Close() potencialmente dar otra excepción si el puerto no es abierto por esta aplicación en este punto? –

+0

No. SerialPort.IsOpen devuelve verdadero si la * instancia * del puerto serie tiene el puerto abierto, no si el puerto serie está abierto en general. Piénsalo como un archivo. Si tienes una instancia de TextReader, llamar a IsOpen no volvería si alguien abre el archivo, solo si tienes el archivo abierto. – FryGuy

0

Esta respuesta llegó a ser un comentario ...

Creo que cuando su programa está en Thread.Sleep (1000) y abre su conexión HyperTerminal, HyperTerminal toma el control del puerto serie. Cuando su programa se despierta e intenta abrir el puerto serie, se lanza una IOException.

Rediseñe su método y trate de manejar la apertura del puerto de una manera diferente.

EDIT: Sobre que usted tiene que reiniciar el ordenador cuando el programa falla ...

Eso probablemente porque su programa NO ES muy cerrado, abra su administrador de tareas y ver si puede encontrar su servicio de programa. Asegúrese de detener todos sus hilos antes de salir de su aplicación.

+0

Mi programa * realmente * está cerrado. Y el hilo de trabajo se detiene utilizando el método Abort() una vez que la aplicación sale.Básicamente, una vez que HyperTerminal haya abierto el puerto una vez, .NET no podrá abrir el mismo puerto hasta que se reinicie. –

+0

Eso es bastante interesante. Voy a ver qué hace HT con mi código, pero primero tengo que encontrar una copia de XP (sin HT en Vista). –

0

¿Hay alguna buena razón para evitar que su servicio "adquiera" el puerto? Observe el servicio integrado de UPS: una vez que le dice que hay un UPS conectado a, digamos, COM1, puede despedirse de ese puerto. Te sugiero que hagas lo mismo a menos que exista un fuerte requisito operacional para compartir el puerto.

+0

Necesito compartir el acceso a un dispositivo de retransmisión de puerta con otra aplicación. Esta aplicación también tiene la amabilidad de liberar el puerto después de que haya terminado con él (es decir, lo ha activado para abrirlo). –

1

Creo que he llegado a la conclusión de que HyperTerminal no funciona bien. Me he encontrado la siguiente prueba:

  1. comenzar mi servicio en el "modo de consola", se inicia la conmutación del dispositivo de encendido/apagado (lo que puedo decir por que es LED).

  2. Inicie HyperTerminal y conéctese al puerto. El dispositivo se mantiene en (HyperTerminal plantea DTR) Mi servicio se escribe en el registro de eventos, que no puede abrir el puerto

  3. parada HyperTerminal, verifico que se ha cerrado correctamente usando el administrador de tareas

  4. Las estancias de dispositivos desactivado (HyperTerminal ha bajado el DTR), mi aplicación sigue escribiendo en el registro de eventos, diciendo que no puede abrir el puerto.

  5. Comienzo una tercera aplicación (la que necesito para coexistir), y le digo que se conecte al puerto. Yo lo hago No hay errores aquí.

  6. Dejo de utilizar la aplicación mencionada anteriormente.

  7. VOILA, mi servicio se activa nuevamente, el puerto se abre con éxito y el LED se enciende/apaga.

2

Cómo hacer comunicaciones asíncronas fiables

No utilizar los métodos de bloqueo, la clase de ayuda interna tiene algunos errores sutiles.

Utilice APM con una clase de estado de sesión, instancias de las cuales administran un búfer y un cursor de búfer compartidos entre llamadas, y una implementación de devolución de llamada que ajusta EndRead en un try...catch. En operación normal, lo último que debe hacer el bloque try es configurar la siguiente devolución de llamada de E/S solapada con una llamada al BeginRead().

Cuando las cosas van mal, catch debe invocar asincrónicamente un delegado a un método de reinicio. La implementación de devolución de llamada debe salir inmediatamente después del bloque catch para que la lógica de reinicio pueda destruir la sesión actual (el estado de la sesión es casi seguro que está dañado) y crear una nueva sesión.El método de reinicio debe ser no implementado en la clase de estado de la sesión porque esto evitaría que destruya y vuelva a crear la sesión.

Cuando el objeto SerialPort está cerrado (lo que sucederá cuando la aplicación sale) puede haber una operación de E/S pendiente. Cuando esto es así, al cerrar SerialPort se activará la devolución de llamada, y bajo estas condiciones EndRead arrojará una excepción que no se puede distinguir de un shitfit de comunicación general. Debe establecer un indicador en su estado de sesión para inhibir el comportamiento de reinicio en el bloque catch. Esto impedirá que el método de reinicio interfiera con el apagado natural.

Se puede confiar en que esta arquitectura no se aferra al objeto SerialPort inesperadamente.

El método de reinicio gestiona el cierre y la reapertura del objeto del puerto en serie. Después de llamar al Close() en el objeto SerialPort, llame al Thread.Sleep(5) para darle la oportunidad de dejarlo ir. Es posible que otra cosa agarre el puerto, así que prepárese para lidiar con esto mientras lo vuelve a abrir.

+0

Consulte la pregunta de Peter en http://stackoverflow.com/questions/385381/how-to-kill-off-a-pending-apm-operation/3869738#3869738 para obtener más detalles sobre este –

Cuestiones relacionadas