Laden...

Threadkonzept - Performanceproblem

Erstellt von all-finder vor 15 Jahren Letzter Beitrag vor 11 Jahren 2.680 Views
A
all-finder Themenstarter:in
30 Beiträge seit 2007
vor 15 Jahren
Threadkonzept - Performanceproblem

Hallo!

Folgendes Verständnisproblem:

Grundlage:

  • Entwicklung auf einem WinCE-System (mit CF 2.0)
  • Busdaten werden empfangen, gesendet und ausgewertet und gespeichert

IST-Zustand:

  • die Mainklasse (Form) zeigt die Daten an, eine Busklasse liest in einem Thread die Daten vom Bus, ein Timer sendet Busdaten, im Lese-Thread werden die Daten auf einer SD-Karte geloggt
  • wenn eine Nachricht empfangen wird, wird diese ausgewertet, angezeigt(im Hauptthread) und ggf. geloggt (im selben Thread)

=> Problem: Aufgrund der hohen Emfangsdichte der Nachrichten werden zu viele Nachrichten nicht abgeholt und somit verloren

SOLL-Konzept:
Ein weiterer Thread für das Logging (Erzeuer/Verbraucher) soll die Daten speichern. Bringt das was? Oder welcher Ansatz wäre zu empfehlen? Kann ich das Logging optimieren?

Danke, mfg

3.511 Beiträge seit 2005
vor 15 Jahren

Erstelle zwei Threads für das Abholen der Daten. Ein Thread nimmt nur die Daten entgegen und macht nichts anderes. Dieser Thread hat eine Liste, in der die Daten abgelegt sind.

Der zweite Thread nimmt sich immer vom ersten Thread die Daten, die ganz unten in der Liste sind. Nach der erfolgreichen Verarbeitung löscht Thread 2 die Daten aus der Liste von Thread 1.

So wäre sichergstellt, das alle Daten ankommen. Und Thread 2 ist nichts weiter als das "Arbeitstier" welches die Daten verarbeitet. Da es zwei getrennte Threads sind, ist es vollkommen egal wie lange Thread 2 für die Verarbeitung braucht. Thread 1 nimmt brav die Daten entgegen.

Das ganze muss natürlich gut synchronisiert sein.

"Jedes Ding hat drei Seiten, eine positive, eine negative und eine komische." (Karl Valentin)

A
all-finder Themenstarter:in
30 Beiträge seit 2007
vor 15 Jahren

Folgende Frage zur sauberen Synchronisation:

wollte eigentlich die Monitor Klasse in Verbindung mit locks verwenden. Natürlich wird jedoch die Wait, Pulse und PulseAll Methode nicht vom CF 2.0 unterstützt. Welche Alternativen habe ich da?
Ist das AutoResetEvent performant genug?

Danke, mfg

3.511 Beiträge seit 2005
vor 15 Jahren

Da bin ich für das CF überfragt 🙂

Aber ich denke mal die Monitor Klasse brauchst du nicht. Würde an sich reichen, wenn du


private object m_Sync = new object();
// Später im Code
lock (this.m_Sync)
{
  Thread1.Liste.Bla();
}

machst. So mach ich es jedenfalls bei solchen Szenarieren. Zwar unter dem großen Bruder vom CF, aber das dürfte egal sein.

Aber das kann dir für das CF garantiert hier jemand noch beantworten.

"Jedes Ding hat drei Seiten, eine positive, eine negative und eine komische." (Karl Valentin)

S
8.746 Beiträge seit 2005
vor 15 Jahren

Sofern du nicht deine Leistung nicht sinnlos verheizt (busy waiting), dann ist deine CE-Plattform eben zu schwachbrüstig. Richtig teuer ist meiner Erfahrung nach die Anzeige. Dreh das mal komplett raus (oder nur jede x-te Botschaft) und schaue wo du stehst.

A
all-finder Themenstarter:in
30 Beiträge seit 2007
vor 15 Jahren

Hallo!
Danke für die Antworten!
Hab jetzt mal alles etwas umdesignt, dennoch ändert sich nix. Jenachdem wie ich die Thread Priorität verdrehe, entweder läuft mein Puffer voll, oder ich verliere nachrichten, oder im worstcase ist die gui nicht mehr ansprechbar. hab den code etwas optimiert, nicht wirklich merkbar.

noch eine Frage bzw. dem Warten, habe bisher immer ein Thread.Sleep(0), wenn z.B. der CAN Treiber keine Nachrichten im Puffer hat. Ist das so sinnvoll?

mfg

W
872 Beiträge seit 2005
vor 15 Jahren

Thread.Sleep(0) bringt Dir nix - Dein Programm wird dort unnötig Ressourcen verbrauchen.
Wenn die Monitor-Klasse nicht vollständig unterstützt wird, dann ist es wahrscheinlich fraglich, ob Du überhaupt mit Threads arbeiten solltest, da der Overhead von Threads nur etwas bringt, wenn Du solche Techniken verfügbar sind. Ich denke, dass Threads nur dann Sinn machen, wenn Du eine klare Richtung hast und nur mit lock auskommst (z.B. für das Loggen, wo Du jede Sekunde mal schauen kannst, ob es etwas zu loggen gibt).

S
8.746 Beiträge seit 2005
vor 15 Jahren

Mal ehrlich: Ohne Code können wir dir keine Tipps geben oder Fehler aufzeigen...

A
all-finder Themenstarter:in
30 Beiträge seit 2007
vor 15 Jahren

okay, so siehts aus (etwas vereinfacht), falls was wichtiges zum Verständnis fehlt, einfach Bescheid sagen.


//Klasse CANHandler
public void startCANRead()
        {
            //config thread
            CANReadThreadIsAlive = true;
            CANReadThread = new Thread(CANReadData);
            CANReadThread.Priority = ThreadPriority.BelowNormal;
            
            //message counter
            count = 0;

            CANReadThread.Start();
        }
private void CANReadData()
        {
            messagesReceived = 0;
            messagesLost = -1;

            Int64 s1=0, s2=0;
            //Stopwatch stopWatch = new Stopwatch();

            while (CANReadThreadIsAlive)
            {
                CanPort.CanEvent ev;
                Object newMessage = new Object();              

                try
                {
                    /* Read events until error */
                    while (pCAN.ReadEventData(out ev) == 0)
                    {
                         //...
                         //Nachrichten auslesen & in Arraylist schaufeln
                         //...

                         //breaking reading
                         if (!CANReadThreadIsAlive) 
                         {
                             CANReadThreadIsAlive = false;
                             count = 0;
                             frmMain.Instance.ThreadStopped();
                             break;
                         }

                    }
                    Thread.Sleep(0);
                }
                catch
                {
                    //empty, jump into, if CE message queue is empty (normal just a few ms) 
                    Thread.Sleep(0);
                }

                /* Wait for next event */
                if (pCAN.WaitCommEvent(out mask) != 0)
                    throw new CanPortException("Error in WaitCommEvent()"); 
   
             }
        }
public void stopCANRead()
        {
            CANReadThreadIsAlive = false;
            count = 0;
            //loggingStep = 0;

            frmMain.Instance.ThreadStopped();
        }
                 

//Klasse Logger
public void startLogging(string folder)
        {
            //...
            //file anlegen usw.

            try
            {
               //...
               //feste daten in file schreiben, fehler vermeiden usw.

                if (isLogging)
                {
                    //config thread
                    MessageLoggerThreadIsAlive = true;
                    MessageLoggerThread = new Thread(saveMessagesThread);
                    MessageLoggerThread.Priority = ThreadPriority.BelowNormal;
                    MessageLoggerThread.Start();
                    
                    }
            }
            catch (Exception e)
            {
                    //Fehler abfangen usw.
             }
        }

public void saveMessagesThread()
        {
            while (MessageLoggerThreadIsAlive)
            {
                if (messageQueue.getNumber() == 0)
                    Thread.Sleep(0);
                else
                    saveMessage(messageQueue.get());
            }
        }


public void saveMessage(Object o)
        {
              //...
              //Daten aus Objekten lesen und in asc file schreiben per Streamwirter
              sWriter.Flush();
        }

//Klasse MessageQueue
public class MessageQueue
    {
        public static readonly MessageQueue Instance = new MessageQueue();

        private ArrayList list = new ArrayList(); 
        private static Object mutex = "";
        
        private MessageQueue () {}

        public Object get()
        {
            Object o;

            lock (mutex)
            {
                if (list.Count > 0)
                {
                    o = list[0];
                    list.RemoveAt(0);
                    return o;
                }
                return null;
            }
        }

        public void insert(Object o)
        {
            lock (mutex)
            {
                list.Add(o);
            }
        }

        public int getNumber()
        {
            return list.Count;
        }
    }

A
all-finder Themenstarter:in
30 Beiträge seit 2007
vor 15 Jahren

Hallo!
keine Antwort? Was für alternativen habe ich zum "Umschalten auf die anderen Threads"? Bin davon ausgegangen, dass ich mit Thread.Sleep(0) den aktuellen Thread die Ressourcen entziehe und ein anderer weiter arbeiten kann.?

mfg

W
872 Beiträge seit 2005
vor 15 Jahren

Du wechselst schon mit Thread.Sleep(0) den Thread, aber die Frage ist, wann wieder der Kontext-Switch zurueck erfolgt - ich wuerde auf den Flush verzichten und den automatisch durchfuehren lassen, wenn der Buffer wirklich voll ist - sonst sehe ich nix, was ein Problem sein sollte.

S
8.746 Beiträge seit 2005
vor 15 Jahren

Ich vermute, du hast ein GC-Problem. Wenn du viele Can-Botschaften bekommst, rennst du im CF locker alle 2-3 Sekunden in einen GC-Lauf. Ich würde - falls möglich - statt mit Objekten mit Strukturen arbeiten.

Wenn möglich solltest du mal mit den Performancecountern die Zahl der GC-Läufe ermitteln.

A
all-finder Themenstarter:in
30 Beiträge seit 2007
vor 15 Jahren

Danke für die Antworten. Aktuell hat sich einiges verbessert indem ich
den StreamWriter


Encoding enc = Encoding.ASCII;
sWriter = new StreamWriter(path, false, enc, 2200);

fest konfiguriert habe und zusätzlich das Flushen selbst übernehme. Jetzt verliere ich nur noch wenige Daten.

Die Puffergröße habe ich mir wie folgt erreichnet:
63-68 Zeichen pro Zeile ~ 70
alle 30 Zeilen lasse ich ihn schreiben + 100 Puffer.

Bemerke per Performancecounter schon Veränderungen um eine ganze Stelle. Jedoch nur alle 4-20 Durchläufe. Hätte gedacht, dass dies durchs Threading kommt. Wie kann ich direkt abfragen, ob der GC aktiv ist?

Notfalls bleibt mir noch die Möglichkeit, direkt im CANRead Thread Strings zu basteln und diese in den Puffer für den Logger zu schieben.

Danke, Gruß

L
45 Beiträge seit 2006
vor 11 Jahren

Hallo all-finder oder auch jeder der mir helfen kann,

ist zwar schon etwas her, aber ich habe aktuell das gleiche problem.
Auf dem Windows CE Gerät landen Daten mittels CDLL (auch von mir) und IntPtr in meiner C# Anwendung, die eigentlich nur dazu gedacht ist die Daten über TCP IP an einen "normalen Windows Rechner" zu schicken (TCP IP in C würde ich mir gerne ersparen). Dort werden die Daten angezeigt, bearbeitet und wieder zurückgeschickt.
Das Windows CE Gerät arbeitet dann mit den bearbeiteten Daten weiter.
Dies geschieht in etwa alle 33ms. Kommt ein GC lauf dazwischen, sei es auf dem "normalen Windows Rechner" oder auf dem Windows CE Gerät gehen Daten verloren und das ist nicht akzeptable. Vielleicht hat ja jemand eine Idee, wo und wie ich diese gradlienige Verarbeitung aufbrechen kann, um ab und an etwas Zeit für den GC zu schaffen, ohne das Daten verloren gehen.

Gruß Luciluke