Pregunta

Estoy escribiendo un Servicio de Windows para la comunicación con un lector Serial Mag-stripe y una placa de relé (sistema de control de acceso).

Me encuentro con problemas en los que 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 muestra inicia con éxito el hilo de trabajo, y la apertura / cierre y elevación de DTR hace que mi lector de banda magnética se encienda (espere 1 segundo), se apague (espere 1 segundo) y así sucesivamente.

Si inicio HyperTerminal y se conecta al mismo puerto COM, HyperTerminal me dice que el puerto está actualmente en uso. Si presiono repetidamente ENTER en HyperTerminal, para intentar volver a abrir el puerto tendrá éxito después de algunos reintentos.

Esto tiene el efecto de causar IOExceptions en mi hilo de trabajo, lo cual se espera. Sin embargo, incluso si cierro HyperTerminal, sigo teniendo la misma IOException en mi hilo de trabajo. La única cura es en realidad reiniciar la computadora.

Otros programas (que no usan bibliotecas .NET para acceso a puertos) parecen funcionar normalmente en este punto.

¿Alguna idea de lo que está causando esto?

¿Fue útil?

Solución

@thomask

Sí, Hyperterminal de hecho habilita fAbortOnError en el DCB de SetCommState, lo que explica la mayoría de las IOExceptions lanzadas por el objeto SerialPort. Algunas PC / computadoras de mano también tienen UART que tienen el indicador de aborto por error activado de forma predeterminada, por lo que es imperativo que la rutina de inicio de un puerto serie lo borre (lo que Microsoft no hizo). Recientemente escribí un artículo largo para explicar esto con mayor detalle (ver esto si estás interesado).

Otros consejos

No puede cerrar la conexión de otra persona a un puerto, el siguiente código nunca funcionará:

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

Debido a que su objeto no abrió el puerto, no puede cerrarlo.

También debe cerrar y desechar el puerto serie incluso después de que se produzcan excepciones

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

Si desea que el proceso sea interrumpible, debe verificar si el puerto está abierto y luego retroceder por un período y luego intentar nuevamente, algo así.

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

¿Ha intentado dejar el puerto abierto en su aplicación, y simplemente activó / desactivó DtrEnable y luego cerró 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 la semántica DTR, así que no sé si esto funcionaría.

Cómo hacer comunicaciones asíncronas confiables

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

Utilice APM con una clase de estado de sesión, cuyas instancias administran un búfer y un cursor de búfer compartido entre llamadas, y una implementación de devolución de llamada que envuelve EndRead en un try ... catch . En funcionamiento normal, lo último que debe hacer el bloque try es configurar la siguiente devolución de llamada de E / S superpuesta con una llamada a BeginRead () .

Cuando las cosas salen mal, catch debe invocar asincrónicamente a 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 corrupto) y crear una nueva sesión. El método de reinicio debe no implementarse en la clase de estado de sesión porque esto evitaría que destruyera y recreara la sesión.

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

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

El método de reinicio gestiona el cierre y la reapertura del objeto del puerto serie. Después de llamar a Close () en el objeto SerialPort , llame a Thread.Sleep (5) para darle la oportunidad de soltarlo. Es posible que algo más tome el puerto, así que prepárate para lidiar con esto mientras lo vuelves a abrir.

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

  1. Iniciar mi servicio en " modo consola " ;, comienza a encender / apagar el dispositivo (puedo decirlo por su LED).

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

  3. Detener HyperTerminal, verifico que está correctamente cerrado usando el administrador de tareas

  4. El dispositivo permanece apagado (HyperTerminal ha reducido 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 (con la que necesito coexistir) y le digo que se conecte al puerto. Yo lo hago No hay errores aquí.

  6. Paro la aplicación mencionada anteriormente.

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

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

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

Este código parece funcionar correctamente. Lo probé en mi máquina local en una aplicación de consola, usando 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();
            }
        }
    }

Esta respuesta se hizo larga para ser un comentario ...

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

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

EDITAR: Sobre eso, debe reiniciar su computadora cuando su programa falla ...

Eso probablemente porque su programa no está realmente cerrado, abra su administrador de tareas y vea si puede encontrar el servicio de su programa. Asegúrese de detener todos sus hilos antes de salir de su aplicación.

¿Hay una buena razón para evitar que su servicio sea "propietario" ¿el puerto? Mire el servicio de UPS incorporado: 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 operativo para compartir el puerto.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top