2009-06-10 21 views
3

Ich habe einen SerialPort-Code, der ständig Daten von einer seriellen Schnittstelle (zum Beispiel COM1) lesen muss. Dies scheint jedoch sehr CPU-intensiv zu sein und wenn der Benutzer das Fenster bewegt oder viele Daten im Fenster angezeigt werden (wie die Bytes, die über die serielle Leitung empfangen werden), wird die Kommunikation gestört.System.IO.Ports.SerialPort und Multithreading

Berücksichtigung der folgenden Code:

void port_DataReceived(object sender, SerialDataReceivedEventArgs e) 
{ 

byte[] buffer = new byte[port.ReadBufferSize]; 

var count = 0; 

try 
{ 
    count = port.Read(buffer, 0, buffer.Length); 
} 
catch (Exception ex) 
{ 
    Console.Write(ex.ToString()); 
} 

if (count == 0) 
    return; 

//Pass the data to the IDataCollector, if response != null an entire frame has been received 


var response = collector.Collect(buffer.GetSubByteArray(0, count)); 

if (response != null) 
{ 
    this.OnDataReceived(response); 
} 

Der Code muss gesammelt werden, da der Datenstrom, der konstant ist und die Daten hat für (Frames/Pakete) werden analysiert.

port = new SerialPort(); 

    //Port configuration code here... 

    this.collector = dataCollector; 

    //Event handlers 
    port.DataReceived += new SerialDataReceivedEventHandler(port_DataReceived); 
    port.Open(); 

Wenn es keine Interaktion Benutzer ist und nichts zum Fenster hinzugefügt wird, dies funktioniert gut, aber sobald es Interaktion Kommunikation wirklich erhält, ist vermasselt. Timeouts auftreten etc ....

Zum Beispiel vermasselt dies alles auf:

Dispatcher.BeginInvoke(new Action(() => 
{ 
    var builder = new StringBuilder(); 
    foreach (var r in data) 
    { 
     builder.AppendFormat("0x{0:X} ", r); 
    } 


    builder.Append("\n\n"); 

    txtHexDump.AppendText(builder.ToString()); 

    txtHexDump.ScrollToEnd(); 


}),System.Windows.Threading.DispatcherPriority.ContextIdle); 
}); 

Aber auch einfache Anrufe zu Problemen führen log4net.

Gibt es Best Practices Serialport Kommunikation oder kann mir jemand sagen, zu optimieren, was mache ich falsch ...

Update:

Wenn die oben nicht viel Sinn gemacht hat . Ich habe ein sehr einfaches (und dumm) kleines Beispiel:

class Program 
{ 
    static void Main(string[] args) 
    { 
     var server = new BackgroundWorker(); 
     server.DoWork += new DoWorkEventHandler(server_DoWork); 
     server.RunWorkerAsync(); 

     var port = new SerialPort(); 
     port.PortName = "COM2"; 
     port.Open(); 
     string input = ""; 

     Console.WriteLine("Client on COM2: {0}", Thread.CurrentThread.ManagedThreadId); 
     while (input != "/quit") 
     { 
      input = Console.ReadLine(); 
      if (input != "/quit") 
      { 
       var data = ASCIIEncoding.ASCII.GetBytes(input); 
       port.Write(data, 0, data.Length); 
      } 
     } 

     port.Close(); 
     port.Dispose(); 
    } 

    static void server_DoWork(object sender, DoWorkEventArgs e) 
    { 
     Console.WriteLine("Listening on COM1: {0}", Thread.CurrentThread.ManagedThreadId); 
     var port = new SerialPort(); 
     port.PortName = "COM1"; 
     port.Open(); 

     port.ReceivedBytesThreshold = 15; 
     port.DataReceived += new SerialDataReceivedEventHandler(port_DataReceived); 
    } 

    static void port_DataReceived(object sender, SerialDataReceivedEventArgs e) 
    { 
     var port = (SerialPort)sender; 
     int count = 0; 
     byte[] buffer = new byte[port.ReadBufferSize]; 
     count = ((SerialPort)sender).Read(buffer, 0, buffer.Length); 

     string echo = ASCIIEncoding.ASCII.GetString(buffer,0,count); 
     Console.WriteLine("-->{1} {0}", echo, Thread.CurrentThread.ManagedThreadId); 
    } 
} 

Das Ergebnis könnte wie folgt aussehen:

Zuhören auf COM1: 6 Client auf COM2: 10 Dies ist einige Beispieldaten, die ich sende ---> 6 Dies ist einige Beispieldaten, die ich

So liest die Daten aus dem Port senden auf dem Hauptthread passiert ....

könnte dieser Teil von dem, was meine Probleme verursacht?

+0

Wo wird der letzte Codeabschnitt aufgerufen? Woher kommt die Variable "Daten"? –

+0

Der obige Code wird aufgerufen, wenn der IDataCollector das Ereignis auslöst, um anzuzeigen, dass er einen vollständigen Datenrahmen erfasst hat. Es ist in dem WPF Windows. Aber selbst wenn Sie log.Warn (".....") Anweisungen im Code der IDataTransportServer oder IDataCollector Implementierungen hinzufügen, wird die SerialCommunication geschraubt ... Die 'Daten' sind einfach, was wir aus der Serie gelesen haben Zeile: puffer.GetSubByteArray (0, count) – TimothyP

+0

Fast jeder Code wird dazu führen, dass die serielle Kommunikation schief geht ... – TimothyP

Antwort

4

Ihre letzte Schlussfolgerung, dass das Ereignis im Hauptthread ausgeführt wird, stimmt möglicherweise nicht für Windows App. Testen Sie das nicht in einer Konsole.

Der richtige Weg, diese zu stimmen ist:

  • einen ausreichend großen Puffer eingestellt, obwohl die Mindest 4096 in der Regel in Ordnung ist

  • die ReceivedBytesThreshold so hoch wie erträglich gesetzt (und tun es vor die Open())

  • so wenig wie möglich in das Empfangene Ereignis, übergeben Sie die Daten an eine Warteschlange oder MemoryStream, wenn Sie mehr Ti benötigen ich

2

Sie sollten Verfahren umschreiben port_DataReceived Daten zu lesen, bis port.BytesToRead größer als Null ist, wie folgt aus:

private void port_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e) 
{ 
     var port = (SerialPort)sender; 
     while (port.BytesToRead > 0) 
     { 
      int byte_count = port.BytesToRead; 
      byte[] buffer = new byte[byte_count]; 

      int read_count = port.Read(buffer, 0, byte_count); 

      // PROCESS DATA HERE 

     } 
} 

Auch würde ich Ihnen empfehlen, nur Daten in Warteschlangenliste in Prozedur port_DataReceived einfügen und Daten durchführen Verarbeitung in einem separaten Thread.

+0

Wenn ich das tue, wartet es irgendwann für immer .... – TimothyP

+0

Wartet wo, in Port_DataReceived() function oder port_DataReceived() wird nicht aufgerufen? Es gibt einige wichtige Sache, die ich nicht im ursprünglichen Beitrag geschrieben habe: - Port_DataReceived ist Callback-Funktion und es führt in seinem eigenen Thread. Es wird immer dann aufgerufen, wenn Daten auf den COM-Port geschrieben werden. ABER Aufruf an port_DataReceived Funktion kann fehlgeleitet werden, wenn neue Daten an den COM-Port geschrieben werden und Sie immer noch nicht vom vorherigen Aufruf zurückgegeben werden, dh Daten innerhalb der Funktion verarbeiten, so ist es wichtig, die Port.BytesToRead-Eigenschaft zu überprüfen Funktion verlassen. –

1

Die klassische Lösung ist, einen FIFO-Puffer zu haben. Stellen Sie sicher, dass die Größe des FIFO groß genug ist, um jeden kritischen Fall zu bewältigen, bei dem viele Eingaben vorliegen und der Prozessorblock belegt ist.

Man könnte sogar ein 2-Puffersystem:

--->|Reader|-->FIFO-->|Processor|--->FIFO2--->|Displayer| 
11

Ich bin überrascht, niemand diese gefangen. Die SerialPort-Klasse verwendet bei Verwendung des DataReceived-Ereignisses einen eigenen Thread. Das bedeutet, wenn der Abonnent z. B. auf Formularelemente zugreift, muss dies entweder mit einer Invoke-Methode oder einer BeginInvoke-Methode erfolgen. Andernfalls enden Sie mit einer Cross-Thread-Operation. In älteren Versionen von .net würde dies unbemerkt mit unberechenbarem Verhalten (abhängig von den CPU-Kernen im PC) und in späteren Versionen eine Ausnahme auslösen.

+0

+1, Während ich bin mir nicht sicher, dass dies die Frage beantwortet, ist es entscheidende Informationen, die mich in der Vergangenheit ein bisschen gebissen hat. – Brad