in meinem Programm erstelle ich einen Task, der in einer Unterklasse längere Aktionen ausführen soll: Webabrufe, Umwandlunge u.ä.
Die Ergebnisse soll das Programm mithilfs Invoke in ein DataGridView schreiben.
SubClass scl = new SubClass()
{
dgv = MeineTabelle
};
Task<string> task = Task.Factory.StartNew(scl.Tuwat);
if(string.IsNullOrEmpty(task.Result) return;
// Weiteres in der Hauptklasse
// in der Unterklasse scl:
void string Tuwat()
{
for (int i...)
{
// Webabruf(e)...
dgv.Invoke(...);// delegate(...) ist korrekt, dgv wurde korrekt übergeben
}
return "xyz";
}
Am dgv.Invoke bleibt das Programm hängen, wartet... Habe ich an den Tasks etwas falsch verstanden?
Danke für Tipps und Hinweise!
Generell kann man das alles roh machen, wie Du das versuchst (StartNew() allein ist hier jedoch suboptimal, hier lieber Task.Run()); aber eigentlich gibts dafür bereits fertige Infrastruktur.
Der Task sollte seine Resultate nicht direkt in die UI pushen, sondern Ergebnisse entweder in einen Zwischenspeicher pushen, oder über ein Event verfügbar machen.
Also ganz einfach, simples [Artikel] Drei-Schichten-Architektur
Die fertige Infrastruktur, mit der Du nachlaufende Prozesse, die in Tasks ausgelagert werden, nennt sich DataFlows. Datenfluss (Task Parallel Library)
Funktionsweise:
Es gibt eine Pipeline, in die Du etwas wirfst, zB eine Url, die abgerufen werden soll.
Die Pipeline hat nun verschiedene Schritte (Download, Parse, Umwandlung...) etc, wie Du es selbst beschrieben hast.
Und dann wird das Ergebnis über ein Event oder eine Zwischenschicht (zB Reactive Extension ObservableCollections) zur Verfügung gestellt.
Auha! Danke für deine ausführliche Antwort, ich werde etwas Zeit brauchen, um alles zu durchforsten.
Die Zeile 6, "string.IsNullOrEmpty..." ist doch aber nötig. Ich muss das Resultat auf null prüfen, denn es wird weiter verarbeitet, und bei task.Result == null stürzt das Programm ab.
Das Realisieren mit dem Zwischenspeicher ist mit noch nicht klar, ich belese mich weiter.
Mit Task.Run... kam ich nicht weiter, sollte wohl von selbst starten, tat es aber nicht. Meine Vorlage: Task Klasse
Es läuft ja die gesamte for-Schleife in dem Task. An der Stelle ist nix parallel. Ich wollte noch zwei Prozesse zusätzlich parallel laufen lassen, aber wenn ich nicht mal mit einem klarkomme ......schluchz! :-))
TPL hatte ich schon gesehen, aber noch nicht gelesen.
Die Notwendigkeit der Prüfung des Invoke habe ich unterlassen, da das Ergebnis auf jeden Fall true ist. Auslesen aus 'nem DGV geht ohne, einschreiben nur mit.
Ich wollte eigentlich, dass das Dgv seine zeilen aktuell bekommt - als eine Art deutlich sichtbarem Fortschrittsbalken. Mit Thread(newThreadStart()) funktionierte es, deswegen war ich überrascht, dass es bei Task nicht läuft.
Die Zeile 6, "string.IsNullOrEmpty..." ist doch aber nötig.
Nein, ist es nicht, wenn Du async/await korrekt anwendest.
Hab Dir auch den Link gegeben, in dem steht, dass Du .Result nicht verwenden sollst. Denke nicht mal 1% der .NET Entwickler werden den eigentlichen Sinn von .Result verwenden (müssen); in 99% ist es ein (unwissentlicher, aus Bequemlichkeit) Missbrauch, der die im Link beschriebenen negativen Nebenwirkungen hat, die dann Fehler auslösen können, die dann schwer zu finden / reproduzieren sind.
In einer vollständig durchängigen asynchronen Implementierung wirst Du aber niemals Task.Run oder Task.StartNew brauchen.
Siehe Asynchrone Programmierung in C# und ConfigureAwait FAQ/ für das Zusammenspiel zum Sync Context der UI.
Zitat von MoaByter
An der Stelle ist nix parallel. Ich wollte noch zwei Prozesse zusätzlich parallel laufen lassen, aber wenn ich nicht mal mit einem klarkomme ......schluchz! :-))
Asynchrone Programmierung und parallele Programmierung sind zwei unterschiedliche Dinge, die sich jedoch dank Tasks relativ einfach zusammen umsetzen lassen.
Fertige Konzepte gibt es, siehe TPL Docs.
Zitat von MoaByter
Auslesen aus 'nem DGV geht ohne, einschreiben nur mit.
Das ist in diesem Fall "Glück" aber nicht korrekt.
Zitat von MoaByter
Mit Thread(newThreadStart()) funktionierte es, deswegen war ich überrascht, dass es bei Task nicht läuft.
Das funktioniert auch mit Tasks super einfach; Du musst es nur korrekt anwenden, was aktuell nicht der Fall ist.
Hab Dir auch den Link gegeben, in dem steht, dass Du .Result nicht verwenden sollst. Denke nicht mal 1% der .NET Entwickler werden den eigentlichen Sinn von .Result verwenden (müssen); in 99% ist es ein (unwissentlicher, aus Bequemlichkeit) Missbrauch, der die im Link beschriebenen negativen Nebenwirkungen hat, die dann Fehler auslösen können, die dann schwer zu finden / reproduzieren sind.
Hast du evtl. den falschen Link gepostet, denn der bezieht sich auf die TPL (nicht Task) - da steht zumindestens nichts von Task.Result?
Gefunden habe ich z.B. C# Async Antipatterns ("4. Blocking on tasks with .Result or .Wait").
Hallo MoaByter,
und dies ist dann auch der Grund warum dein bisheriger Code hängenbleibt. Du blockierst mittels task.Result den UI-Thread und in deinem Nebenthread wird versucht den UI-Thread (per Invoke) anzusprechen (der aber gerade auf das Ende der Methode wartet -> Deadlock!).
Die Notwendigkeit der Prüfung des Invoke habe ich unterlassen, da das Ergebnis auf jeden Fall true ist. Auslesen aus 'nem DGV geht ohne, einschreiben nur mit.
Bitte NIEMALS die Daten in ein UI Control hineinfrickeln, sondern Databinding benutzen.
Dazu auch [Artikel] Drei-Schichten-Architektur beachten.
Aber meiner Erfahrung nach hat DataBinding auch so seine Tücken in MultiThreaded Szenarien.
Ich scheitere z.B. trotz Jahrelanger Erfahrung in der Entwicklung immer noch daran, in Windows Forms eine vernünftige Synchronisierung hinzubekommen. Denn nur davon, dass ich meine BindingList oder ähnliches ans Grid pappe kann ich trotzdem nicht einfach in einem anderen Thread Einträge bearbeiten, entfernen und hinzufügen ohne das mir eine entsprechende Exception um die Ohren fliegt.
Wissen ist nicht alles. Man muss es auch anwenden können.
Geht recht einfach mit einer abgeleiteten ObservableCollection ( lässt sich einfacher überschreiben ) die einen Windows.Forms.Timer hat.
Dann die einzelnen Aktionen abfangen und ggf den Timer starten. Der Timer macht dann die Events im UI Thread.
Für die UI ist es selten nötig in millisekunden anzuzeigen.
Aber das ist ein weiterer Grund mal WPF anzuschauen.
Erst einmal ein braves Dankeschön an euch alle: Abt, Th69, FZelle, inflames2k!
Bisher hatte ich in meinem Programm mit BackgroundWorkern gearbeitet, bei dem das ReportProgress schwierig und aufwändig war, bis man mir sagte, der sei veraltet, besser seien Threads oder Tasks. Mit Threads funktierte alles recht gut, nur die Datenüber- und -rückgabe gestaltete sich problematisch. Also Tasks? Die sind offensichtlich auch nicht ganz ohne, zu denen fehlt mir noch das Wissen. Auch wenn Abt meint, das sei auch mit Tasks super einfach ... wenn man weiß wie's geht, stimmt das wohl, wenn nicht, muss man sich durchbeißen und lernen.
Zu meiner Person: Ich komme aus der HTML/javascript-Ecke, davorvorvor TI-58 (programmierbarer Taschenrechner), Schul-, Studienzeit Mitte 70er Jahre).
Mit diesem meinem Programm begann ich 2013 C# zu lernen - es sollte drei Aufgaben erledigen, dann kamen Webseitenanalysen mit Down- und Uploads dazu. Mittlerweile ist es auf eine Größe angewachsen, bei der es mir echt schwerfällt, den Überblick zu behalten: etwa 2,5MB an selbstgeschrieben .cs-Dateien.
Deswegen dachte ich auch schon an das:
3-Schicht-Modell: Ich bin am Umbau. Da das Programm aber weiter täglich funktionieren muss, ist das nicht ganz trivial. Und da ich es damals noch nicht kannte, hatte ich den KISS-Weg gewählt - überaus erfolgreich, wesegen ich jetzt vor dem Dilemma stehe.
Parallele Programmierung: mit Tasks, TPL, await & Co. sind für mich noch sehr neu - mal abgesehen vom BackgroundWorker.
Deadlock: Irgendwas in dieser Richtung vermutete ich schon, konnte nur nicht den Grund dafür finden, stellte aber schon fest, dass das Programm einfach stehen blieb und nur mit dem Taskmanager abschaltbar war. Der Hauptthread schien tot.
DataBinding: Das habe ich bisher noch nicht genutzt, sondern alle Daten brav & artig in die Tabellen und parallel dazu in Variablen geschrieben, müssen halt nur gleichermaßen aktuell gehalten werden. Nicht ganz einfach, aber mit etwas Sorgfalt zu schaffen.
Async Antipatterns: Bin noch am Lesen, vieles ist mir allerdings schon klar - und davor gewesen, auch wenn ich diese Probleme noch nicht hatte.
ObservableCollection: Das klingt interessant. Mal sehen, was ich darüber erfahre.
Im Moment kämpfe ich allerdings noch mit VS2019Com, davor VS2017Com, das Debugging ist mir kaum möglich, ich bekomme jede Menge Meldungen, "Der Wert der Variable kann nicht angezeigt werden, da sie wegoptimiert wurde". Auch Haltepunkte werden mal eben übersprungen. Aber das gehört zu einer anderen Anfrage, die ich noch erstellen muss. Jetzt muss erstmal mein Programm wieder laufen, sonst gibt's Ärger vom Chef.
Gehabt euch wohl, bleibt gesund - wie gut, dass ein Computer sich nicht mit C. infizieren kann :-)), viel Erfolg bei euren Aktivitäten,
viele Grüße - ...lypô aus Moabit.
Auch wenn Abt meint, das sei auch mit Tasks super einfach ... wenn man weiß wie's geht, stimmt das wohl, wenn nicht, muss man sich durchbeißen und lernen.
Also das "super einfach" is nich nur aus Komplexitätssicht so von mir gesagt worden, sondern auch aus Code-Sicht: Du brauchst mit async/await viel viel weniger Quellcode, um das Ziel zu erreichen als mit Threads / anderen Mechanismen.
Dass Tasks und das Async/Await-System erstmal eine Lernhürde darstellen: absolut!
Dass Tasks und das Async/Await-System erstmal eine Lernhürde darstellen: absolut!
Das meinte ich. Aber durch muss ich, wenn's mit dem Programm was werden soll. Ich geh' mal davon aus, dass ich den Minijob auch bei Rente behalten werde.
Verstehe ich das mit dem DataBinding richtig: Wenn sich die Datenquelle ändert, wird diese Änderung automatisch ins Dgv übernommen? Ich habe für meine Tabellen eine textbasierte Datenbankdatei, Erweiterung .mtgt. Da stehen die Tabellenformatierungsdaten drin, 9 Zeilen, und die Zelleninhalte (Text). Die kann ich aber nicht anbinden/auswählen, vermutlich weil kein definiertes Protokoll existiert. Ich habe allerdings eine Klasse, mit der ich die Tabellen aus den Dateien lade und in einer Variable speichere, aber ob man das Protokoll nennen darf? Übrigens werden die Datenbankeinträge tatsächlich nur und ausschließlich programmgesteuert vorgenommen. wenn da was schief läuft, muss ich die Software ändern, da die aufegrufene Quelle/Webseite sich geändert hat. Ich benötige also keine Eingabemaske für die Datenbank.
Da denke ich mir, ich könnte in dem Feld für die Variable eine Exception starten, wenn sie geändert wird - oder wie startet man ein Event? Aufgrund dieses Events/dieser Exception müsste dann die zugehörige Tabelle neu geladen werden. Ginge sowas? Ich bastele gerade daran.
Ah: event – C#-Referenz
Ich könnte auch pro Tabelle ein verstecktes Textfeld anlegen mit "OnTextChange"-Event.
Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von MoaByter am .
Um DataBinding mit WinForms und dem DataGridView (DGV) zu verstehen, empfehle ich den Artikel A Detailed Data Binding Tutorial (bes. auch den dazugehörigen Source-Code zum Testen und Ausprobieren).
Generell solltest du dein Programm nach und nach auf die 3-Schichten Architektur umstellen (d.h. Trennung von UI, Logik und Datenzugriff), dadurch vereinfacht sich vieles am Programmcode (und du bist nicht auf dauernde Workarounds angewiesen).
Ein interessanter Artikel - warum hab' ich den bei meiner Suche nicht gefunden? Bedankt jedenfalls!
Mal sehen, ob ich das so bei mir einbauen kann, einige Anpassungen muss ich vornehmen, da die Tabellenformatierung bei mir in der jeweiligen Datei notiert ist. Und ich muss die Zeilen-, Zellenfarbe ändern können, könnte klappen.
Den Bearbeitungscode in meinem Programm muss ich regelmäßg prüfen und b.B. anpassen, da die Webseiten, die ich - erlaubt - auslese, auch weiterentwickelt werden.
Tatsächlich könnte ich mit DataBinding auch an anderen Stellen viel Code vereinfachen/ersparen.
Teilweise bzw. in Ansätzen ist das 3-Schicht-Modell schon realisiert, da ziemlich schnell klar wurde, dass es kompliziert wird. Mangels Informationen - und Zeit - habe ich es leider auf "später...!" verschoben, jetzt rächt sich das ... naja, is' ja noch nicht zu spät.
Also euch allen herzlichen Dank für eure konstruktiven Beiträge, sie haben mir sehr geholfen. Manchmal braucht's eben ein paar Gedankenanstöße - fertige Lösungen bringen nichts, da ich's dann nie lerne. Schließlich hab' ich mit dem Bau des Programms C# in der praktischen Anwendung gelernt. So ist's doch eher überraschend, dass das Programm jetzt seit 8 Jahren recht problemarm läuft.