Pergunta

Eu estou escrevendo um serviço do Windows para a comunicação com um leitor de Mag-stripe Serial e uma placa de relé (sistema de controle de acesso).

eu me deparo com problemas onde o código pára de funcionar (eu recebo IOExceptions) após o outro programa "interrompido" o processo, abrindo a mesma porta serial como o meu serviço.

Uma parte do código é a seguinte:

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

O meu programa de amostra é iniciado com êxito o trabalho-thread, ea abertura / fechamento e levantando da DTR faz com que o meu leitor Mag-stripe ao poder up (esperar 1seg), desligar (aguarde 1 seg) e assim por diante.

Se eu lançar HyperTerminal e conecta-se a mesma porta COM, HyperTerminal me diz que o porto está atualmente em uso. Se eu pressionar repetidamente ENTER no HyperTerminal, para tentar reabrir o porto vai ter sucesso depois de algumas tentativas.

Isto tem o efeito de causar IOExceptions no meu trabalho-thread, o que é esperado. No entanto, mesmo se eu fechar HyperTerminal, eu ainda obter o mesmo IOException no meu trabalho-thread. A única cura é realmente para reiniciar o computador.

Outros programas (que não está usando .NET bibliotecas para a porta de acesso) parecem funcionar normalmente neste momento.

Todas as ideias sobre o que está causando isso?

Foi útil?

Solução

@thomask

Sim, Hyperterminal não de fato permitir fAbortOnError em DCB do SetCommState, o que explica para a maioria dos IOExceptions lançadas pelo objeto SerialPort. Alguns PCs / handhelds também têm UARTs que têm a abortar na bandeira de erro ativado por padrão - por isso é imperativo que uma série de porta o init limpa rotina (o que a Microsoft deixou de fazer). Eu escrevi um longo artigo recentemente para explicar isso em maior detalhe (ver este se você estiver interessado).

Outras dicas

Você não pode fechar alguma outra pessoa conexão a uma porta, o seguinte código nunca vai funcionar:

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

Porque o seu objeto não abriu a porta que não pode fechar-lo.

Além disso, você deve fechar e descartar a porta serial, mesmo depois de exceções ocorrem

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

Se você quiser que o processo seja passível de interrupção, em seguida, você deve verificar se a porta está aberta e, em seguida, de volta ao largo por um período e tente novamente, algo como.

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

Você tentou deixar a abrir a porta na sua aplicação, e apenas girando DtrEnable on / off, e em seguida, fechando a porta quando seu fecha aplicação? ou seja:

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

Eu não estou familiarizado com a semântica DTR, então eu não sei se isso iria funcionar.

Como fazer confiável assíncrona comms

Não use os métodos de bloqueio, a classe auxiliar interno tem alguns bugs sutis.

Use APM com uma classe de estado de sessão, exemplos dos quais gerenciar um buffer e buffer cursor compartilhado entre chamadas, e uma implementação de retorno de chamada que envolve EndRead em um try...catch. Em operação normal, a última coisa que o bloco try deve fazer é configurar a próxima I sobreposta / O retorno de chamada com uma chamada para BeginRead().

Quando as coisas dão errado, catch deve de forma assíncrona invocar um delegado para um método de reinício. A implementação de retorno de chamada deve sair imediatamente após o bloco catch modo que a lógica de reinício pode destruir a sessão atual (estado da sessão é quase certamente corrupto) e criar uma nova sessão. O mosto método restart não ser implementado na classe de estado de sessão, porque isso iria impedi-lo de destruir e recriar a sessão.

Quando o objeto SerialPort está fechado (o que vai acontecer quando o aplicativo sai) pode muito bem haver uma operação pendente I / O. Quando isso acontece, fechando o SerialPort irá desencadear a chamada de retorno, e sob estas condições EndRead vai accionar uma excepção que é indistinguível de um Comms shitfit geral. Você deve definir um sinalizador em seu estado sessão para inibir o comportamento de reinicialização no bloco catch. Isto irá parar o seu método de reinício de interferir com desligamento natural.

Esta arquitetura pode ser invocado não para agarrar o objeto SerialPort inesperadamente.

O método reinicialização administra o fechamento e reabertura do objecto porta serial. Depois de chamar Close() no objeto SerialPort, chamada Thread.Sleep(5) para dar-lhe a chance de deixar ir. É possível que alguma outra coisa para agarrar a porta, para estar pronto para lidar com isso enquanto re abrindo-lo.

Eu acho que eu vim à conclusão de que HyperTerminal não joga bem. Eu executar o seguinte teste:

  1. Comece meu serviço no "modo de console", ele começa a mudar o dispositivo on / off (i pode dizer por que é LED).

  2. Iniciar HyperTerminal e se conectar à porta. As estadias aparelho (HyperTerminal levanta DTR) Meu serviço grava no log de eventos, que não pode abrir a porta

  3. Parar HyperTerminal, eu verificar se está bem fechada usando o gerenciador de tarefas

  4. As estadias dispositivo off (HyperTerminal baixou DTR), meu aplicativo continua a escrever para o log de eventos, dizendo que não pode abrir a porta.

  5. I começar uma terceira aplicação (o que eu preciso para coexistir com), e dizer-lhe para se conectar à porta. Eu fá-lo. Sem erros aqui.

  6. Eu parar o acima mencionado aplicação.

  7. VOILA, meus serviços entra em ação novamente, a porta se abre com êxito eo LED vai ON / OFF.

Eu tentei mudar o trabalho-thread assim, com o mesmo resultado. Uma vez HyperTerminal uma vez consegue "captar a porta" (enquanto o meu fio está dormindo), o meu serviço não será capaz de abrir a porta novamente.

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 corretamente. Eu testei-o em minha máquina local em um aplicativo de console, utilizando Procomm Plus para abrir / fechar a porta, eo programa continua passando.

    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 resposta tem muito tempo para ser um comentário ...

Eu acredito que quando seu programa está em um Thread.Sleep (1000) e você abrir sua conexão HyperTerminal, o HyperTerminal assume o controle sobre a porta serial. Quando o programa, em seguida, acorda e tentando abrir a porta serial, um IOException é lançada.

Redesenhar o seu método e tentar lidar com a abertura da porta de uma maneira diferente.

EDIT: Sobre que você tem que reiniciar o computador quando o programa falhar ...

Isso provavelmente porque seu nsi't programa realmente fechado, abra o seu taskmanager e veja se você pode encontrar o seu serviço de programa. Certifique-se de parar todos os seus tópicos antes de sair sua aplicação.

Existe uma boa razão para manter o seu serviço de "possuir" a porta? Olhe para o built-in serviço de UPS - uma vez que você diga que há uma UPS ligados a, por exemplo, COM1, você pode beijar aquele adeus porta. Eu sugiro que você faça o mesmo a menos que haja um requisito operacional forte para compartilhar a porta.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top