it-swarm.com.de

Wie mache ich eine robuste SerialPort-Programmierung mit .NET/C #?

Ich schreibe einen Windows-Dienst für die Kommunikation mit einem seriellen Magnetstreifenleser und einer Relaiskarte (Zugriffskontrollsystem).

Ich stoße auf Probleme, bei denen der Code nicht mehr funktioniert (ich erhalte IOExceptions), nachdem ein anderes Programm den Prozess durch Öffnen derselben seriellen Schnittstelle wie mein Dienst "unterbrochen" hat.

Ein Teil des Codes lautet wie folgt:

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

Mein Beispielprogramm startet den Arbeitsthread erfolgreich und das Öffnen/Schließen und Anheben des DTR bewirkt, dass mein Magnetstreifenleser hochfährt (1 Sekunde warten), herunterfährt (1 Sekunde warten) und so weiter.

Wenn ich HyperTerminal starte und eine Verbindung zu demselben COM-Port herstelle, teilt HyperTerminal mir mit, dass der Port derzeit verwendet wird. Wenn ich in HyperTerminal wiederholt die EINGABETASTE drücke, um zu versuchen, den Port erneut zu öffnen, ist dies nach einigen Wiederholungsversuchen erfolgreich.

Dies hat zur Folge, dass in meinem Arbeitsthread IOExceptions auftreten, was zu erwarten ist. Auch wenn ich HyperTerminal beende, wird in meinem Arbeitsthread dieselbe IOException angezeigt. Die einzige Möglichkeit besteht darin, den Computer neu zu starten.

Andere Programme (die keine .NET-Bibliotheken für den Portzugriff verwenden) scheinen an diesem Punkt normal zu funktionieren.

Irgendwelche Ideen, was dies verursacht?

25
Thomas Kjørnes

@thomask

Ja, Hyperterminal aktiviert fAbortOnError tatsächlich in DCB von SetCommState. Dies erklärt die meisten IOExceptions, die vom SerialPort-Objekt ausgelöst werden. Einige PCs/Handhelds verfügen auch über UARTs, bei denen das Kennzeichen Abbruch bei Fehler standardmäßig aktiviert ist. Es ist daher unbedingt erforderlich, dass die Initialisierungsroutine einer seriellen Schnittstelle dies löscht (was Microsoft nicht getan hat). Ich habe kürzlich einen langen Artikel geschrieben, um dies näher zu erläutern (siehe dies wenn Sie interessiert sind).

24
Zach Saw

Sie können keine andere Verbindung zu einem Port herstellen. Der folgende Code wird nie funktionieren:

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

Da Ihr Objekt den Port nicht geöffnet hat, können Sie ihn nicht schließen.

Sie sollten die serielle Schnittstelle auch nach Auftreten von Ausnahmen schließen und entsorgen

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

Wenn Sie möchten, dass der Prozess unterbrechbar ist, sollten Sie prüfen, ob der Port geöffnet ist, und dann für eine gewisse Zeit wieder ausschalten. Versuchen Sie es dann erneut.

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

Haben Sie versucht, den Port in Ihrer Anwendung geöffnet zu lassen, DtrEnable ein-/auszuschalten und den Port zu schließen, wenn Ihre Anwendung geschlossen wird? das heißt:

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

Ich bin nicht mit der DTR-Semantik vertraut, daher weiß ich nicht, ob dies funktioniert.

2
FryGuy

Wie man zuverlässige async-Kommunikationen durchführt

Verwenden Sie keine Blockierungsmethoden, da die interne Hilfsklasse einige kleine Fehler enthält.

Verwenden Sie APM mit einer Sitzungsstatusklasse, deren Instanzen einen Puffer und einen Puffercursor verwalten, die für alle Aufrufe gemeinsam verwendet werden, und eine Callback-Implementierung, die EndRead in einen try...catch umschließt. Im normalen Betrieb sollte der try-Block als letztes den nächsten überlappten E/A-Callback mit einem Aufruf an BeginRead() einrichten. 

Wenn etwas schief geht, sollte catch einen Delegaten asynchron zu einer Restart-Methode aufrufen. Die Callback-Implementierung sollte unmittelbar nach dem catch-Block beendet werden, damit die Neustartlogik die aktuelle Sitzung zerstören kann (der Sitzungsstatus ist höchstwahrscheinlich beschädigt) und eine neue Sitzung erstellt. Die Neustartmethode muss nicht in der Sitzungsstatusklasse implementiert sein, da dies verhindern würde, dass die Sitzung zerstört und neu erstellt wird.

Wenn das SerialPort-Objekt geschlossen wird (was passieren wird, wenn die Anwendung beendet wird), kann es durchaus zu einer anstehenden E/A-Operation kommen. Wenn dies der Fall ist, löst das Schließen des SerialPort den Callback aus, und unter diesen Bedingungen wird EndRead eine Ausnahme auslösen, die sich nicht von einer allgemeinen Kommunikationsshitfit unterscheidet. Sie sollten in Ihrem Sitzungsstatus ein Flag setzen, um das Neustartverhalten im catch-Block zu verhindern. Dadurch wird verhindert, dass die Neustartmethode das natürliche Herunterfahren beeinträchtigt.

Es kann davon ausgegangen werden, dass sich diese Architektur nicht unerwartet auf das SerialPort-Objekt beschränkt.

Die Restart-Methode verwaltet das Schließen und das erneute Öffnen des Objekts der seriellen Schnittstelle. Nachdem Sie Close() für das SerialPort-Objekt aufgerufen haben, rufen Sie Thread.Sleep(5) auf, um ihm die Möglichkeit zu geben, loszulassen. Es ist möglich, dass der Port von etwas anderem erfasst wird. Machen Sie sich also bereit, dies zu tun, während Sie ihn erneut öffnen.

2
Peter Wone

Ich habe versucht, den Arbeitsthread so zu ändern, mit dem exakt gleichen Ergebnis. Wenn HyperTerminal einmal erfolgreich den Port "erfasst" hat (während sich mein Thread im Ruhezustand befindet), kann mein Dienst den Port nicht mehr öffnen.

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
Thomas Kjørnes

Dieser Code scheint richtig zu funktionieren. Ich habe es auf einem lokalen Computer in einer Konsolenanwendung getestet, indem ich den Port mit Procomm Plus öffne/schließe und das Programm tickt weiter.

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

Ich glaube, ich bin zu dem Schluss gekommen, dass HyperTerminal nicht gut spielt. Ich habe den folgenden Test ausgeführt:

  1. Starten Sie meinen Dienst im "Konsolenmodus". Er schaltet das Gerät ein/aus (ich kann es an der LED erkennen).

  2. Starten Sie HyperTerminal und stellen Sie eine Verbindung zum Port her. Das Gerät bleibt eingeschaltet (HyperTerminal erhöht DTR) Mein Dienst schreibt in das Ereignisprotokoll, dass der Port nicht geöffnet werden kann

  3. Stoppen Sie HyperTerminal. Ich vergewissere mich, dass der Task-Manager ordnungsgemäß geschlossen ist

  4. Das Gerät bleibt ausgeschaltet (HyperTerminal hat die DTR gesenkt), meine App schreibt weiterhin in das Ereignisprotokoll und sagt, dass der Port nicht geöffnet werden kann.

  5. Ich starte eine dritte Anwendung (die, mit der ich koexistieren muss) und sage, dass sie sich mit dem Port verbinden soll. Ich mache das Keine Fehler hier.

  6. Ich stoppe den oben genannten Antrag.

  7. VOILA, mein Service startet wieder, der Port öffnet sich erfolgreich und die LED geht an/aus.

1
Thomas Kjørnes

Gibt es einen guten Grund, Ihren Dienst davon abzuhalten, den Hafen zu "besitzen"? Schauen Sie sich den eingebauten USV-Service an. Sobald Sie wissen, dass eine USV an COM1 angeschlossen ist, können Sie diesen Port zum Abschied küssen. Ich würde vorschlagen, dass Sie dasselbe tun, es sei denn, es besteht eine starke betriebliche Anforderung, den Port gemeinsam zu nutzen.

0
Coderer

Diese Antwort wurde zu lange, um ein Kommentar zu sein ...

Wenn sich Ihr Programm in einem Thread.Sleep (1000) befindet und Sie Ihre HyperTerminal-Verbindung öffnen, übernimmt das HyperTerminal die Kontrolle über die serielle Schnittstelle. Wenn Ihr Programm dann aufwacht und versucht, die serielle Schnittstelle zu öffnen, wird eine IOException ausgelöst.

Überarbeiten Sie Ihre Methode neu und versuchen Sie, das Öffnen des Ports anders zu behandeln.

BEARBEITEN: Darüber müssen Sie Ihren Computer neu starten, wenn Ihr Programm fehlschlägt ...

Wahrscheinlich weil Ihr Programm nicht wirklich geschlossen ist, öffnen Sie Ihren Taskmanager und sehen Sie, ob Sie Ihren Programmdienst finden können. Stoppen Sie unbedingt alle Threads, bevor Sie Ihre Anwendung beenden.

0
Anders R