Domanda

Sto scrivendo un servizio Windows per la comunicazione con un lettore seriale a banda magnetica e una scheda relè (sistema di controllo accessi).

Riscontro problemi in cui il codice smette di funzionare (ottengo IOExceptions) dopo che un altro programma ha "interrotto" il processo aprendo la stessa porta seriale del mio servizio.

Parte del codice è la seguente:

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

Il mio programma di esempio avvia correttamente il thread di lavoro e l'apertura / chiusura e l'innalzamento del DTR causano l'accensione del mio lettore di bande magnetiche (attendere 1 secondo), lo spegnimento (attendere 1 secondo) e così via.

Se avvio HyperTerminal e mi connetto alla stessa porta COM, HyperTerminal mi informa che la porta è attualmente in uso. Se premo ripetutamente INVIO in HyperTerminal, per provare a riaprire la porta riuscirà dopo alcuni tentativi.

Ciò ha l'effetto di causare IOExceptions nel mio thread di lavoro, che è previsto. Tuttavia, anche se chiudo HyperTerminal, ottengo sempre la stessa IOException nel mio thread di lavoro. L'unica cura è in realtà riavviare il computer.

Altri programmi (che non utilizzano librerie .NET per l'accesso alle porte) sembrano funzionare normalmente a questo punto.

Qualche idea su cosa sta causando questo?

È stato utile?

Soluzione

@thomask

Sì, Hyperterminal abilita infatti fAbortOnError nel DCB di SetCommState, che spiega la maggior parte delle IOExceptions generate dall'oggetto SerialPort. Alcuni PC / palmari hanno anche UART che hanno il flag di interruzione su errore attivato per impostazione predefinita, quindi è imperativo che la routine di inizializzazione di una porta seriale la cancelli (cosa che Microsoft ha trascurato di fare). Di recente ho scritto un lungo articolo per spiegarlo in modo più dettagliato (vedi this se sei interessato).

Altri suggerimenti

Non puoi chiudere la connessione di qualcun altro a una porta, il seguente codice non funzionerà mai:

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

Poiché il tuo oggetto non ha aperto la porta non puoi chiuderlo.

Inoltre, è necessario chiudere e smaltire la porta seriale anche dopo che si sono verificate eccezioni

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

Se si desidera che il processo sia interrompibile, è necessario verificare se la porta è aperta e quindi tornare indietro per un periodo e quindi riprovare, qualcosa di simile.

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

Hai provato a lasciare la porta aperta nella tua applicazione, ad attivare / disattivare DtrEnable e quindi a chiudere la porta alla chiusura dell'applicazione? cioè:

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

Non ho familiarità con la semantica DTR, quindi non so se funzionerebbe.

Come eseguire comunicazioni asincrone affidabili

Non usare i metodi di blocco, la classe helper interna ha alcuni bug sottili.

Usa APM con una classe di stato della sessione, le cui istanze gestiscono un buffer e un cursore del buffer condivisi tra le chiamate e un'implementazione di callback che avvolge EndRead in un try ... catch . Durante il normale funzionamento, l'ultima cosa che dovrebbe fare il blocco try è impostare il successivo callback I / O sovrapposto con una chiamata a BeginRead () .

Quando le cose vanno male, catch dovrebbe invocare in modo asincrono un delegato a un metodo di riavvio. L'implementazione del callback dovrebbe uscire immediatamente dopo il blocco catch in modo che la logica di riavvio possa distruggere la sessione corrente (lo stato della sessione è quasi certamente corrotto) e creare una nuova sessione. Il metodo di riavvio deve essere non implementato sulla classe dello stato della sessione perché ciò impedirebbe che distrugga e ricrea la sessione.

Quando l'oggetto SerialPort è chiuso (cosa che succederà all'uscita dall'applicazione) potrebbe esserci un'operazione di I / O in sospeso. In questo caso, la chiusura di SerialPort attiverà il callback e in queste condizioni EndRead genererà un'eccezione che non è distinguibile da un shitfit di comunicazione generale. È necessario impostare un flag nello stato della sessione per inibire il comportamento di riavvio nel blocco catch . Ciò impedirà al tuo metodo di riavvio di interferire con l'arresto naturale.

Si può fare affidamento su questa architettura per non trattenerla in modo imprevisto sull'oggetto SerialPort.

Il metodo di riavvio gestisce la chiusura e la riapertura dell'oggetto porta seriale. Dopo aver chiamato Close () sull'oggetto SerialPort , chiama Thread.Sleep (5) per dargli la possibilità di lasciarsi andare. È possibile che qualcos'altro afferri il porto, quindi sii pronto a gestirlo riaprendolo.

Penso di essere giunto alla conclusione che HyperTerminal non gioca bene. Ho eseguito il seguente test:

  1. Avvia il mio servizio in " modalità console " ;, inizia ad accendere / spegnere il dispositivo (posso dirlo dal suo LED).

  2. Avvia HyperTerminal e connettiti alla porta. Il dispositivo rimane acceso (HyperTerminal genera DTR) Il mio servizio scrive nel registro eventi che non è in grado di aprire la porta

  3. Ferma HyperTerminal, verifico che sia chiuso correttamente usando il task manager

  4. Il dispositivo rimane spento (HyperTerminal ha abbassato il DTR), la mia app continua a scrivere nel registro eventi, dicendo che non può aprire la porta.

  5. Avvio una terza applicazione (quella con cui devo coesistere) e le dico di connettersi alla porta. Lo faccio. Nessun errore qui.

  6. Fermo l'applicazione sopra menzionata.

  7. VOILA, il mio servizio si attiva di nuovo, la porta si apre correttamente e il LED si accende / spegne.

Ho provato a cambiare il thread di lavoro in questo modo, con lo stesso esatto risultato. Una volta HyperTerminal riesce una volta a "catturare la porta" (mentre il mio thread è inattivo), il mio servizio non sarà in grado di riaprire la porta.

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

Questo codice sembra funzionare correttamente. L'ho provato sul mio computer locale in un'applicazione console, usando Procomm Plus per aprire / chiudere la porta e il programma continua a spuntare.

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

Questa risposta è diventata troppo lunga per essere un commento ...

Credo che quando il tuo programma è in Thread.Sleep (1000) e apri la tua connessione HyperTerminal, HyperTerminal prende il controllo della porta seriale. Quando il programma si sveglia e cerca di aprire la porta seriale, viene generata un'IOException.

Riprogetta il tuo metodo e prova a gestire l'apertura della porta in modo diverso.

EDIT: A tale proposito devi riavviare il computer quando il programma non riesce ...

Probabilmente perché il tuo programma non è veramente chiuso, apri il tuo task manager e vedi se riesci a trovare il servizio del tuo programma. Assicurati di interrompere tutti i thread prima di uscire dall'applicazione.

C'è un buon motivo per impedire al tuo servizio di "possedere" " il porto? Guarda il servizio UPS integrato: una volta che hai detto che c'è un UPS collegato, per esempio, COM1, puoi dire addio a quella porta. Ti suggerirei di fare lo stesso a meno che non ci sia un forte requisito operativo per condividere la porta.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top