Autor Beitrag
Leitstelle_V
Hält's aus hier
Beiträge: 7



BeitragVerfasst: Mi 01.04.20 23:04 
Hallo,

ich bin mehr oder weniger Programmierneuling und habe letzte Woche erst angefangen, C# zu lernen. Davor habe ich jahrelang gar nichts programmiert, meine letzten Programmiersprachen waren TurboPascal und QBasic (wisst ihr Bescheid...).

Nun habe ich ein erstes Programm geschrieben. Funktion ist folgende:

In Liste 1 sammeln sich Einträge ("Eingänge") mit einem Zeitstempel. Mit DateTime.Now.AddSeconds habe ich simuliert, dass diese zu unterschiedlichen Zeiten eintreffen. Zehn Sekunden nach deren Eingang sollen sie aus der Liste gelöscht werden. Die verstrichene Zeit seit Eingang läuft unten mit. Das beginnt, sobald das Programm lädt.

Mit dem Button "Element hinzufügen" kann ein neuer Eintrag in der Liste erzeugt werden, der dann wieder zehn Sekunden nach Eingang gelöscht wird... Auch dann, wenn die Liste bereits vollständig geleert wurde.

Das Programm funktioniert. Ich habe es umgesetzt, indem ich mit einem Timer, der auf 1 Millisekunde gestellt ist, eine Schleife erschaffen hab, falls man das so sagen kann. Ich habe das gleiche mit einer while-Schleife probiert, aber das funktioniert nicht. Irgendwie löscht er da alle Einträge dann erst auf einen Schlag und aktualisiert auch nicht die Anzeige der verstrichenen Zeit... Deswegen ist die while-Schleife im Programm auskommentiert und der entsprechende Button für die Schleife ohne Funktion...

Ich habe nun mehrere Fragen:

1. Ich hoffe es ist nachvollziehbar, wie die vergangenen Sekunden seit Eingang errechnet werden. Dafür brauche ich mehrere Schritte und bestimmt kann man da noch etwas vereinfachen und damit beschleunigen, aber ich weiß noch nicht, wie...

2. Ist meine Lösung mit dem Timer die richtige/beste? Ich kann an anderer Stelle des späteren Codes den Timer noch ausschalten, solange kein Eingang in der Liste erfolgt, damit er nicht immer wieder durchrennt. Aber wenn dann eben was eingegangen ist, sollte ich dann weiter so mit dem Timer arbeiten bzw. lässt sich dieser noch optimieren (< 1 ms?) oder wären andere Methoden (z. B. while-Schleife) generell schneller bzw. reccourcenschonender?

3. Und letztendlich, warum funktioniert meine while-Schleife nicht?

Ich danke euch vielmals.

LG
Max

PS: Projekt anbei, hier der Code:

ausblenden volle Höhe C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
namespace WindowsFormsApp13
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            listBox1.Items.Add("Eingang: " + DateTime.Now);
            listBox1.Items.Add("Eingang: " + DateTime.Now.AddSeconds(10));
            listBox1.Items.Add("Eingang: " + DateTime.Now.AddSeconds(18));
            listBox1.Items.Add("Eingang: " + DateTime.Now.AddSeconds(25));
            listBox1.Items.Add("Eingang: " + DateTime.Now.AddSeconds(31));
         }

        private void button1_Click(object sender, EventArgs e)
        {
            /* while (listBox1.Items.Count > 0)
             {
                string ErsteZeile = listBox1.Items[0].ToString();
                string ErsteZeileEingangsZeit = ErsteZeile.Substring(ErsteZeile.Length - 19);
                DateTime EingangsZeit = Convert.ToDateTime(ErsteZeileEingangsZeit);
                DateTime AktuelleZeit = DateTime.Now;
                TimeSpan Differenz = AktuelleZeit - EingangsZeit;
                double VergangeneSekunden = Differenz.TotalSeconds;
                if (VergangeneSekunden > 10) listBox1.Items.RemoveAt(0);
                label1.Text = "Sekunden vergangen seit Eingang (Löschung bei 10): " + VergangeneSekunden;
             }*/


        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            if (listBox1.Items.Count > 0)
            {
                string ErsteZeile = listBox1.Items[0].ToString();
                string ErsteZeileEingangsZeit = ErsteZeile.Substring(ErsteZeile.Length - 19);
                DateTime EingangsZeit = Convert.ToDateTime(ErsteZeileEingangsZeit);
                DateTime AktuelleZeit = DateTime.Now;
                TimeSpan Differenz = AktuelleZeit - EingangsZeit;
                double VergangeneSekunden = Differenz.TotalSeconds;
                if (VergangeneSekunden > 10) listBox1.Items.RemoveAt(0);
                label1.Text = "Sekunden vergangen seit Eingang (Löschung bei 10): " + VergangeneSekunden;
            }
        }

        private void button2_Click(object sender, EventArgs e)
        {
            listBox1.Items.Add("Eingang: " + DateTime.Now);
        }
    }
}



Moderiert von user profile iconTh69: Topic aus C# - Die Sprache verschoben am Do 02.04.2020 um 08:30
Einloggen, um Attachments anzusehen!
Palladin007
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1282
Erhaltene Danke: 182

Windows 11 x64 Pro
C# (Visual Studio Preview)
BeitragVerfasst: Do 02.04.20 04:25 
Vorweg: Was Du mit Frage 1 meinst, wird mir nicht ganz klar.
Frage 3 und 2:

Was Du hier anschneidest, ist vermutlich das ekligste Thema, was Du dir als Einstieg aussuchen konntest: Multithreading :D

Grob umschrieben:
Dein Programm hat einen Thread, dieser Thread arbeitet die Befehle ab. Willst Du parallel dazu etwas anderes machen lassen, brauchst Du einen zweiten Thread. Threads können auch untereinander kommunizieren, aber das ist nicht ganz so einfach, da Du nie weißt, was der andere Thread gerade tut und ob er mit deinen Aktionen klar kommt, oder nicht. Aus dem Grund ist es elementar wichtig, dass ein Thread nie ungeprüft, auf etwas zugreift, was der andere Thread bearbeiten kann und wahlweise dem anderen Thread signalisiert, dass er kurz warten muss, wenn er mit den selben Daten arbeiten will.

Die Benutzeroberfläche hat einen eigenen Thread (UI-Thread), der alle UI-Aktionen abarbeitet, wie z.B. die Button-Clicks oder Aktualisierungen, allerdings mit einem Nachteil: Die UI mag es gar nicht, wenn ein anderer Thread an den Controls herum spielt.
Wenn Du nun einen Button Click und dadurch eine unendliche While-Schleife startet, dann arbeitet dein Thread unendlich lange an dieser While-Schleife und hat nie wieder Zeit für die Benutzeroberfläche.
Das kann man umgehen, indem man einen zweiten Thread startet und die Schleife darin laufen lässt, allerdings kann man darin nichts tun, da immer die UI betroffen wäre. Du musst also alle für die UI relevanten Aktionen wieder in den UI-Thread synchronisieren.
Doch auch dafür gibt es eine Lösung: Der UI-Thread ist im Prinzip auch eine Schleife, der ständig guckt, ob es etwas zu tun gibt, man nennt das ganze Konstrukt den Dispatcher. Wenn Du etwas für den UI-Thread hast, kannst Du diese Aufgabe nehmen und dem Dispatcher hin stellen, der wird das dann zum nächstmöglichen Zeitpunkt im UI-Thread abarbeiten. Darauf kannst Du dann warten, oder auch nicht.
Aber in jedem Fall gilt: Länger andauernde Aufgaben sollten nicht im UI-Thread laufen, da die UI solange blockiert wird.

Das klingt alles super kompliziert, aber dafür hat sich Microsoft was cooles ausgedacht: Der Timer
Der macht das alles ganz automatisch, der wartet in einem anderen Thread die angegebene Zeit und im Anschluss gibt er die von dir bestimmte Aufgabe an den Dispatcher, sodass Du dich nicht um den UI-Thread sorgen musst.
Aus dem Grund solltest Du auch erst beim Timer bleiben.

Aber ganz ehrlich: Eine Millisekunde?
Du wärst der erste Mensch, der den Unterschied wahrnehmen kann, besonders da die Zeit für normale Menschen in Sekunden gemessen wird. Ich würde das auch 500ms oder gleich 1s setzen, das spart Leistung.



Und ob es eine bessere Herangehensweise gibt: Die Frage kann man ausnahmslos immer mit Ja beantworten.
Es gibt immer und für alles einen besseren Weg, die Frage ist, ob es sich lohnt, nach einem besseren Weg zu suchen.
Wenn Du dazu aber noch die Uhrzeit aktualisiert haben möchtest, kommst Du unter WinForms nicht um einen Timer herum - außer Du baust etwas, was ein ähnliches Ergebnis liefert.

Komplexere Ideen hab ich viele :D

Eine Idee, grob umschrieben:

In dem anderen Thread:
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
while (IsLoopRunning)
{
    // Suche nach dem nächsten Eintrag zum Entfernen.
    // Berechne die Zeit bis der Eintrag entfernt werden muss.
    // Warte bis diese Zeit abgelaufen ist, oder unendlich, wenn kein Eintrag da ist, oder bis eine Art Marker signalisiert wird.
    // Entferne alle Einträge, die entfernt werden können - das wird dann natürlich als Aufgabe dem Dispatcher übergeben.
}


Im UI-Thread, wenn ein Eintrag hinzugefügt wird:
ausblenden C#-Quelltext
1:
2:
// Füge Eintrag hinzu
// Signalisiere den Marker


Das Ergebnis wäre, dass der Thread solange nichts tut, bis es einen Eintrag zum Entfernen gibt, oder ein neuer Eintrag hinzugekommen ist.
Er berechnet dabei eigenständig die nächste Wartezeit und für den Marker gibt es verschiedene Klassen, die solche Signale zuverlässig zwischen zwei Thread transportieren können.

Für diesen Beitrag haben gedankt: Leitstelle_V
Leitstelle_V Threadstarter
Hält's aus hier
Beiträge: 7



BeitragVerfasst: Do 02.04.20 10:27 
Hallo Palladin007,

vielen Dank für deine ausführlichen Erläuterungen. Ich hab alles verstanden, was du gesagt hast, wie es in der Theorie funktioniert, aber ich glaube praktisch umsetzen ist was anderes :-D. Erstmal zu meiner 1. Frage, die dir nicht ganz klar ist.

Um die Sekunden zu berechnen, die vergangen sind, seitdem ein Eintrag in die Liste erfolgte, verwende ich folgenden Code:

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
string ErsteZeile = listBox1.Items[0].ToString();
string ErsteZeileEingangsZeit = ErsteZeile.Substring(ErsteZeile.Length - 19);
DateTime EingangsZeit = Convert.ToDateTime(ErsteZeileEingangsZeit);
DateTime AktuelleZeit = DateTime.Now;
TimeSpan Differenz = AktuelleZeit - EingangsZeit;
double VergangeneSekunden = Differenz.TotalSeconds;


Es wird jeweils einzeln abgearbeitet: Ersten Eintrag in der Liste in String konvertieren, Substring extrahieren, Diesen Substring in Datums-/Uhrzeitformat konvertieren, dann Zeitspanne (Differenz) ausrechnen und dann in Sekunden umrechnen. Ich kann das nur so, durch diese jeweiligen einzelnen Anweisungen, jeder Schritt hat eine eigene Codezeile. Vielleicht (bestimmt) ist das aber der "umständliche" bzw. "langsame" Weg und es lässt sich das ein oder andere zusammenfassen oder anders schreiben?

Zu dem Rest: Nun, ich werde erstmal alles mit Timern aufbauen. Gerade da es mehrere Listen geben wird, die parallel abgearbeitet werden müssen und da ich als Basis auch noch dieses "Fremdprogramm" brauche: "Nur eine Instanz pro Anwendung" featuring IPC, krieg ich das mit Multithreading bestimmt nicht so einfach hin. Ich kann mein Ergebnis ja dann hochladen und vielleicht hast du ja Zeit und Lust, dir das mal anzuschauen...

Vielen Dank bis hierher :-).

LG
Max

Moderiert von user profile iconTh69: URL-Titel hinzugefügt.
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4764
Erhaltene Danke: 1052

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: Do 02.04.20 10:36 
Vllt. wäre statt der ListBox eine ListView (wie z.B. beim Windows-Explorer, mit verschiedenen Spalten, d.h. View = View.Details) hier besser, dann kannst du die Zeit in einer eigenen Spalte anzeigen und einfacher wieder auslesen) - außerdem sieht es für die Anwender auch geordneter aus.
Palladin007
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1282
Erhaltene Danke: 182

Windows 11 x64 Pro
C# (Visual Studio Preview)
BeitragVerfasst: Do 02.04.20 14:42 
Bei WinForms wäre vielleicht eine Kombination aus der ListView (wie Th69 schon erwähnt hat) und DataBinding eine bessere Lösung. Du verwaltest dann nicht die Einträge in der ListView, sondern einzelne Daten-Objekte mit DateTimes, die Du sehr leicht verwalten und durchsuchen kannst. Die ListView bekommt ihre Daten dann per DataBinding mitgeteilt und zeigt sie an.
In gerade letzterem Gebiet bin ich aber kein Experte, ich bin eher bei WPF zuhause und von da kommt auch mein Vorschlag (Stichwort MVVM), allerdings ist MVVM bei WinForms eher umständlich, bringt aber trotzdem viel Potential mit.


Thema Multithreading:
Wie viele Prozesse Du hast, macht da überhaupt keinen Unterschied, die weiteren Prozesse sollen ja sowieso dem Haupt-Prozess mitteilen, das da noch was dazu gekommen ist und dann arbeitet der Haupt-Prozess weiter.
Allerdings musst Du dich darauf einstellen, dass das Event, was durch den anderen Prozess ausgelöst wird, immer in einem anderen Thread läuft, da jeder Prozess seinen eigenen Start-Thread zugewiesen bekommt. Du musst also immer dafür sorgen, dass dein Code threadsafe ist, oder die Aufrufe direkt in den UI-Thread weiter geben (Stichwort Dispatcher und seine Methoden), solange das nicht zu lange dauert und dadurch den UI-Thread blockiert.


Wenn ich dein Projekt aber richtig verstehe, dann sollen die anderen Prozesse eigentlich nur Daten an den Haupt-Prozess übermitteln.
Da gäbe es noch einen anderen Weg, der mMn. auch besser ist: Du baust eine Schnittstelle nach außen und stellst dann ein zweites Mini-Programm hin, was nichts anderes tut, als Daten an die Schnittstelle zu übergeben.

Sprich:
Dein Haupt-Programm läuft auf einem Server und kann über eine Schnittstelle Daten empfangen und verarbeiten.
Das Mini-Neben-Programm kann von überall sonst aufgerufen werden, stört sich nicht daran, wenn es einmal oder 20mal läuft, bekommt Daten per Kommandozeile (z.B. Parameter) übergeben und reicht sie an die Schnittstelle vom Haupt-Programm weiter.
Das Multi-Threading-Thema hast Du dann immer noch, aber Du bist nicht mehr an die das Betriebssystem gebunden und der Aufbau könnte dich im Bezug auf die Software-Architektur in eine richtige Richtung zwingen.

Hierfür würde ich zu WCF raten, das ist zwar veraltet und wird nicht mehr weiter entwickelt, funktioniert aber immer noch hervorragend und ist neben der WebAPI von ASP.NET leichter zu lernen und zu überblicken.
Wenn Du ASP.NET Core dafür verwendest, hast Du eine REST-API, aber auch ein deutlich größeres Framework, doch da bietet Microsoft eine wirklich gute Doku an. Außerdem läuft ASP.NET Core auch auf Linux.

Ob Du das jetzt umstellen möchtest, sei dir überlassen.
Ich würde es so machen, allerdings hab ich auch jahrelang mit so einem Umfeld gearbeitet, ich bin da also zuhause :D
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4764
Erhaltene Danke: 1052

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: Do 02.04.20 16:07 
Leider unterstützt das WinForms-ListView direkt kein DataBinding - dies ist nur mit etwas Aufwand möglich: Data binding a ListView.

Und zum Dispatcher: Ja, dies wird schon per Control.Invoke in dem IPC-Demo Programm so gemacht (sonst käme eine Exception).

Und bzgl. WCF oder Rest-API: das sollte sich ein Anfänger nicht antun.