Laden...

Queue zeitkritisch auslesen

Erstellt von bene1984 vor 16 Jahren Letzter Beitrag vor 16 Jahren 2.412 Views
B
bene1984 Themenstarter:in
19 Beiträge seit 2007
vor 16 Jahren
Queue zeitkritisch auslesen

Hey folks,
ich bin langsam am verzweifeln.

Über einen Server bekomme Daten.

Diese sehen so aus: "Engine RPM xxxx.xx\r\nEngine RPM xxxx.xx\r\nEngine RPM xxxx.xx\r\nEngine RPM xxxx.xx\r\nEngine RPM xxxx.xx\r\n"
mal länger mal kürzer.

So verarbeite ich sie momentan (dataBuffer hat immer die Länge socket.Available):


Queue<float> qu = new Queue<float>();
[...]
private void receive_msg(IASyncResult asyn)
{

readbytes = socket.EndReceive(asyn);
                if (readbytes > 0)
                {
                    IEnumerable<string> msg = System.Text.Encoding.ASCII.GetString(dataBuffer).Split(new String[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries).Where(fifo => fifo.StartsWith("EngineRPM  "));
                    foreach (string element in msg)
                    {
                        qu.Enqueue(Convert.ToSingle(element.Substring(10, element.Length - 10)) / 100);
                    }
                    getData();
                }
                else
                {
                    getData();
                }
}

Ich mache dann mittels des Windows - TimerProc jede 1ms mit der Funktion setRPM(qu.Deque()), natürlich nur wenn qu.Count >0;

Das Problem ist aber, dass sich das Queue viel schneller füllt, als ich es so auslesen kann und ich brauche die Daten aber "Just in Time" 😉 Das heißt ich setze die Daten immer ein Stück versetzt erst wieder ein...

Jemand ne Idee, wie ich das auslesen noch beschleunigen könnte? Oder geht das so gar nicht?

Gelöschter Account
vor 16 Jahren

diesen block:

IEnumerable<string> msg = System.Text.Encoding.ASCII.GetString(dataBuffer).Split(new String[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries).Where(fifo => fifo.StartsWith("EngineRPM  "));
                    foreach (string element in msg)
                    {
                        qu.Enqueue(Convert.ToSingle(element.Substring(10, element.Length - 10)) / 100);
                    }

kannst du unter umständen weglasse bzw wesentlich performanter gestalten.
so wie ich das sehe hat ein informationselement von "Engine RPM xxxx.xx\r\nEngine RPM xxxx.xx\r\nEngine RPM xxxx.xx\r\nEngine RPM xxxx.xx\r\nEngine RPM xxxx.xx\r\n"
ein feste größe von 20

auf einen string kannst du auch per index zugreifen. somit dürfte auch eine simple forschleife und ein wenig grundschulmathematik ausreichen um das wesentlich schnell hinzubekommen.

was allerdings die tatsache betrifft das die messages schneller reinkommen als du sie bearbeiten kannst... da hilft nur blockweise auslesen. im allgemeinen sind .net anwendungen nicht unbedingt für echtzeitsysteme empfehlenswert.

U
1.688 Beiträge seit 2007
vor 16 Jahren

Ich mache dann mittels des Windows - TimerProc jede 1ms mit der Funktion setRPM(qu.Deque())

Jede ms wird das sowieso nicht klappen. Allein die Ausführungszeit Deines Codes wird schon deutlich über 1ms liegen.
Welchen Timer benutzt Du genau? Verwende am besten einen MultimediaTimer mit einem Intervall von 10-20ms.

B
bene1984 Themenstarter:in
19 Beiträge seit 2007
vor 16 Jahren

Ich benutze folgenden Timer:


[DllImport("user32")]
private static extern int SetTimer(IntPtr hwnd, int nIDEvent, int uElapse, TimerProc CB);

delegate void TimerProc(IntPtr hWnd, uint nMsg, uint nIDEvent, int dwTime);

private TimerProc cb = null;

private void Btn_Click(object sender, EventArgs e)
{
cb = new TimerProc(parsing);
z=SetTimer(IntPtr.Zero, 0, 1, cb);
}

private void parsing(IntPtr hWnd, uint nMsg, uint nIDEvent, int dwTime) 
            {
                if (qu.Count > 0)
                {
                    rpm.setValue(qu.Dequeue());
                }
            }

Ich werde das mit dem Indexen mal ausprobieren, das Problem ist bloß, dass auch andere Messages als diese versendet werden, also muss ich eigentlich immer checken, ob das nächste Element mit "EngineRPM " beginnt oder nicht.

Gelöschter Account
vor 16 Jahren

string.StartsWith(string) ist da perfomant genug.

U
1.688 Beiträge seit 2007
vor 16 Jahren

Ich benutze folgenden Timer:

Du musst den Timer konfigurieren (Auflösung). Such mal bei Codeproject oder im Forum. Da hab ich gestern den Link gepostet.

B
bene1984 Themenstarter:in
19 Beiträge seit 2007
vor 16 Jahren

string.StartsWith(string) ist da perfomant genug.

schon, aber eine message kann z.B. auch so aussehen(ich kann das nicht beeinflussen):

"EngineRPM xxxx.xx\r\nvelocity xxx.xx mph\r\nEngineRPM xxxx.xx\r\nEngineRPM xxxx.xx\r\nEngineRPM xxxx.xx\r\nvelocity xxx.xx mph\r\ncoordinates xx.xx xx.xx xx.xx mph\r\n" usw.

Da reicht ein einfaches StartsWith auch nicht.

Ich denke, ich muss string.Split anwenden und alle die dann im StringArray mit "EngineRPM" beginnen berücksichtigen...

@ujr

entweder ich konfiguriere diesen Multimedia Timer falsch, oder TimerProc ist performanter.

Jedenfalls läuft o.g. Code "besser" mit dem TimerProc als dem MultimediaTimer.


               
                t.Tick += new EventHandler(parsing);
                t.Period = 10;
                t.Resolution = 1;
                t.Mode = Multimedia.TimerMode.Periodic;

U
1.688 Beiträge seit 2007
vor 16 Jahren

Jedenfalls läuft o.g. Code "besser" mit dem TimerProc als dem MultimediaTimer.

Das ist unwahrscheinlich, da SetTimer/TimerProc mit Windows-Botschaften arbeitet und nicht sehr genau ist. Wo es hakt, kann ich mir momentan nicht vorstellen.

Zu beachten ist ggf. dass die MultimediaTimer-Callback aus einem eigenen Thread aufgerufen wird - vielleicht hast Du ja Synchronisierungsprobleme?

B
bene1984 Themenstarter:in
19 Beiträge seit 2007
vor 16 Jahren

nun ja, aber was soll ich machen, wenns so besser , also die ausgelesenen Werte wesentlich flüssiger ausgelesen werden?

Was meinst du mit "Syncronisierungsproblem"? Dass Enqueue und Dequeue synchron laufen? Sorry 😉

U
1.688 Beiträge seit 2007
vor 16 Jahren

nun ja, aber was soll ich machen, wenns so besser , also die ausgelesenen Werte wesentlich flüssiger ausgelesen werden?

Hmmm - wahrscheinlich ist die Zeit zwischen TimerTicks zu klein. Verwende mal testweise 20, 30, 40 ms. Daraus dass Du bei SetTimer Millisekunden angeben kannst, folgt noch nicht, dass der Timer dies einhält.

Was meinst du mit "Syncronisierungsproblem"? Dass Enqueue und Dequeue synchron laufen? Sorry 😉

Zugriff auf Daten/GUI aus verschiedenen Threads.

B
bene1984 Themenstarter:in
19 Beiträge seit 2007
vor 16 Jahren

Wo kanns da Probleme geben?

Ich prüfe doch schon, ob qu.Count >0 ist und solange das gilt, kann ja nichts passieren? Seh ich da was falsch?

Ich check das mal mit dem Timer...

U
1.688 Beiträge seit 2007
vor 16 Jahren

Ich prüfe doch schon, ob qu.Count >0 ist und solange das gilt, kann ja nichts passieren? Seh ich da was falsch?

Ja - qu.Count>0 ist nicht atomar, d.h. es kann jederzeit durch einen anderen Thread unterbrochen werden. Problematisch wird das dann, wenn der andere Thread qu verändert. So ist es denkbar, dass gerade wenn Du den Count abgefragt hast, in einem anderen Thread Elemente hinzugefügt werden. Du must also qu vor "gleichzeitigem" Zugriff schützen.

Benutzt Du SetTimer (WM_TIMER) läuft alles über den UI-Thread und das Problem tritt nicht auf.

B
bene1984 Themenstarter:in
19 Beiträge seit 2007
vor 16 Jahren

also ich benutze setTimer() bei einem ButtonClick. Läuft das dann auf dem UI-Thread? Sorry mit Threads hab ich mich noch nicht sooo auseinandergesetzt...

Und mit deinem MultimediaTimer funktionierts einfach nicht. Ich habe die Period in 10er SChritten auf 50ms erhöht und die Resolution auch von 1 auf 50.

Hab jetzt wieder den anderen timer
Das Ding ist halt, es klappt astrein mit dem, NUR fängt er ein paar ms zu spät an mit setRPM. Sonst läuft oben genannter Code genauso, wie ich es mit vorgestellt habe. Also ich habe so ein Delay von ein paar ms, das würde ich gerne beseitigen.

3.971 Beiträge seit 2006
vor 16 Jahren

Würde es dir vllt helfen mehr als nur ein Objekt in einem Zyklus zu dequen? Wenn ja, schau mal hier Bulk Queue-Klasse. Dort hast du die Möglichkeit, beliebig viele Objekte auf einmal aus der Queue zu holen

Es gibt 3 Arten von Menschen, die die bis 3 zählen können und die, die es nicht können...

B
bene1984 Themenstarter:in
19 Beiträge seit 2007
vor 16 Jahren

diese Methode gibt mir ja nur zurück, wieviele Werte dequeued wurden. Dann kann ich ja gar nicht mehr auf den Wert der gerade dequeued wird zugreifen... Bringt mir also auch nicht viel, oder versteh ich was falsch?

3.971 Beiträge seit 2006
vor 16 Jahren

Nein, du übergibst ein (leeres) Array an BulkQueue, dieses wird dann entsprechend gefüllt und die Funktion gibt die Anzahl der dequeden Einträge zurück.


MyQueue<int> queue = ...;
int[] arr = new int[100];
...
int dint = queue.DequeBulk(arr, 0, arr.Length);

Es gibt 3 Arten von Menschen, die die bis 3 zählen können und die, die es nicht können...

B
bene1984 Themenstarter:in
19 Beiträge seit 2007
vor 16 Jahren

Das hat leider auch nicht zum erwünschten erfolg geführt. Die Queue leert sich zwar schneller, aber ich habe dann den zusätzlichen aufwand, das array wieder auszulesen, da ich ja jeden Wert brauche.

Ich glaube, das Problem, wo es letztendlich hakt ist, dass er zu spät merkt, dass qu.Count > 0 ist.


private void parsing(IntPtr hWnd, uint nMsg, uint nIDEvent, int dwTime)
            {
                [B]if (qu.Count > 0)[/B]
                {
                    rpm.setValue(qu.Dequeue());
                }
            }

Ich vermute, wenn ich das lösen kann, wäre alles ok. Sicher weiß ichs nicht.

P.S: wollte mich hier mal ausdrücklich für die schon vielen gebotenen Hilfestellungen bedanken!!!

U
1.688 Beiträge seit 2007
vor 16 Jahren

also ich benutze setTimer() bei einem ButtonClick. Läuft das dann auf dem UI-Thread?

Der WM_TIMER läuft immer im UI Thread.

Sorry mit Threads hab ich mich noch nicht sooo auseinandergesetzt...

Das kann man ja ändern. Das solltest Du auch tun, weil Du wissen musst, in welchem Thread Dein Server läuft und wie Du ggf. Daten schützen musst.

Und mit deinem MultimediaTimer funktionierts einfach nicht. Ich habe die Period in 10er SChritten auf 50ms erhöht und die Resolution auch von 1 auf 50.

Das ist nicht mein MultimediaTimer, sondern einfach der genaueste Timer unter Windows. Wenn es damit nicht klappt, ist irgendwas faul. Behaupte ich mal.

Das Ding ist halt, es klappt astrein mit dem, NUR fängt er ein paar ms zu spät an mit

Warum wohl? Weil er niemals eine Genauigkeit von 1ms erreicht. Soweit ich mich erinnere, hat der UI Timer eine Genauigkeit von 55ms.

Vielleicht schreibst Du mal mehr darüber, wie Du die Daten empfängst.

B
bene1984 Themenstarter:in
19 Beiträge seit 2007
vor 16 Jahren

Das ist nicht mein MultimediaTimer, sondern einfach der genaueste Timer unter Windows. Wenn es damit nicht klappt, ist irgendwas faul. Behaupte ich mal.

Ich meinte damit: "Der Timer, den du mir vorgeschlagen hast".
Evtl. ist was faul.

Vielleicht schreibst Du mal mehr darüber, wie Du die Daten empfängst.

Ich habe oben ja schon die Callback Function für den receive Vorgang gepostet, hier nochmal der komplette vorgang:


        public void receive_msg(IAsyncResult asyn)
        {
            int readbytes = 0;
            try
            {
                readbytes = socket.EndReceive(asyn);
                if (readbytes > 0)
                {
                        string[] msg = System.Text.Encoding.ASCII.GetString(dataBuffer).Split(new String[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
                        for (int i = 0; i < msg.Length; i++)
                        {
                            if (msg[i].StartsWith("EngineRPM"))
                            {
                                _qu.Enqueue(Convert.ToSingle(msg[i].Substring(10, msg[i].Length - 10)) / 100);
                            }
                    }
                    getData();
                }
                else{ getData(); }
            }
            catch (Exception err) { Console.WriteLine(err.ToString()); }

        }
        public void getData()
        {
            try
            {
                dataBuffer = new Byte[socket.Available];
                if (callBack == null)
                {
                    callBack = new AsyncCallback(receive_msg);
                }
                socket.BeginReceive(dataBuffer, 0, socket.Available, SocketFlags.None, callBack, null);
                

            }
            catch (ObjectDisposedException)
            {
                Disconnect();
            }
        }

308 Beiträge seit 2005
vor 16 Jahren

Hallo bene,

blöde Frage, aber warum musst Du BeginReceive benutzen?
Mach doch einen eigenen Thread mit einer Endlosschleife ala (pseudo)

while(true)
{
    if (dataAvail)
    {
         raw=receive;
         data=parse(raw);

         lockWrite(queue) 
               queue.add(data)
         // notify?
    }
}

damit bist du nie out-of-order. Dir kann höchsten der buffer überlaufen.....

/c

B
bene1984 Themenstarter:in
19 Beiträge seit 2007
vor 16 Jahren

So könnt ichs auch machen, aber es geht ja nicht um den receive vorgang an sich, da die Daten, so wies jetzt implementiert ist, schnell genug reinkommen.

Oder hast du einen anderen Gedanken?

308 Beiträge seit 2005
vor 16 Jahren

So könnt ichs auch machen, aber es geht ja nicht um den receive vorgang an sich, da die Daten, so wies jetzt implementiert ist, schnell genug reinkommen.

Das Asynchrone kostet auf jeden Fall etwas zeit (Stack, Thread-Wechsel etc...).

Ich frage mich gerade aber auch, in welcher Geschwindigkeit Daten bei dir Reinkommen...? Und, wie kann ein Client schneller senden, als der Server empfängt???

Wenn du synchron arbeitest sollten nie Daten schneller kommen, als du sie empfangen kannst. (Oder ist das ein Multicast?).

/c

B
bene1984 Themenstarter:in
19 Beiträge seit 2007
vor 16 Jahren

Explizit ist es kein Multicast so denk ich zumindest.

Das ganze ist so aufgebaut:

Ein Client schickt alle 5-10ms Daten einers Simulationsprogramms an einen "Dispatcher", dieser leitet die Daten an alle, die an dem Dispatcher angemeldet sind, sobald er was empfangen hat.

Und ich hol mir die Daten die ich brauche...

B
bene1984 Themenstarter:in
19 Beiträge seit 2007
vor 16 Jahren

Es läuft jetzt...

ich hab nochmal mit den Leuten von der Simulationssoftware gesprochen und die haben jetzt dafür gesorgt dass pro Message nur eine Nachricht versendet wird.

Also nicht
Message = EngineRPM xxxx.xx\r\nEngineRPM xxxx.xx\r\nEngineRPM xxxx.xx\r\nEngineRPM xxxx.xx\r\nEngineRPM xxxx.xx\r\n

sondern

Message1 = EngineRPM xxxx.xx\r\n
Message2 = EngineRPM xxxx.xx\r\n
Message3 = EngineRPM xxxx.xx\r\n
Message4 = EngineRPM xxxx.xx\r\n
Message5 = EngineRPM xxxx.xx\r\n

und dann klappt das super.

Trotzdem danke für Eure vielen Hilfestellungen!!