Laden...

mit Task.Factory.StartNew eine class aufrufen

Erstellt von MoaByter vor 3 Jahren Letzter Beitrag vor 3 Jahren 668 Views
M
MoaByter Themenstarter:in
68 Beiträge seit 2016
vor 3 Jahren
mit Task.Factory.StartNew eine class aufrufen

Hallo,

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!

16.806 Beiträge seit 2008
vor 3 Jahren

Habe ich an den Tasks etwas falsch verstanden?

Ja. Tasks rufen Methoden / Aktionen auf, keine Klassen.

Dann wäre da noch die Zeile if(string.IsNullOrEmpty(task.Result) return; die zu einem absoluten NoGo im Umgang mit Tasks gehört.
Potenzielle Fehler bei Daten- und Aufgabenparallelität

Unterklasse längere Aktionen ausführen soll: Webabrufe, Umwandlunge u.ä.

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.

Machst Du das sauber, dann brauchst Du nirgends ein Invoke, weil das der SyncContext automatisch gerade zieht.
Ein Invoke darfst Du ohnehin nicht blind ausführen; Du musst prüfen, ob der Invoke überhaupt notwendig ist.
[FAQ] Controls von Thread aktualisieren lassen (Control.Invoke/Dispatcher.Invoke)

M
MoaByter Themenstarter:in
68 Beiträge seit 2016
vor 3 Jahren

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.

Und jetzt werde ich lesen...
Bedankt nochmal!

16.806 Beiträge seit 2008
vor 3 Jahren

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.

Statt


Task<string> task = Task.Factory.StartNew(scl.Tuwat);
if(string.IsNullOrEmpty(task.Result) return;

schreibt man eigentlich (bezogen auf diese zwei Zeilen)


string content = await Task.Factory.StartNew(scl.Tuwat)
if(string.IsNullOrEmpty(content) return;

..bzw. in expliziter Form, weil Du Duch im UI Kontext befindest


string content = await Task.Factory.StartNew(scl.Tuwat).ConfigureAwait(true);
if(string.IsNullOrEmpty(content) return;

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.

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.

Auslesen aus 'nem DGV geht ohne, einschreiben nur mit.

Das ist in diesem Fall "Glück" aber nicht korrekt.

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.

4.931 Beiträge seit 2008
vor 3 Jahren

Hallo Abt,

Dann wäre da noch die Zeile if(string.IsNullOrEmpty(task.Result) return; die zu einem absoluten NoGo im Umgang mit Tasks gehört.

>

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!).

16.806 Beiträge seit 2008
vor 3 Jahren

Danke für den Hinweis; wollte eigentlich den (mittlerweile archivierten) Artikel Async/Await - Best Practices in Asynchronous Programming verlinken.

F
10.010 Beiträge seit 2004
vor 3 Jahren

Noch eine kleine Anmerkung:

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.

Und ja, das geht auch in WindowsForms

2.298 Beiträge seit 2010
vor 3 Jahren

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.

PS Fritz!Box API - TR-064 Schnittstelle | PS EventLogManager |

F
10.010 Beiträge seit 2004
vor 3 Jahren

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.

16.806 Beiträge seit 2008
vor 3 Jahren

Reactive Extensions und Reactive UI können das von Haus aus.

M
MoaByter Themenstarter:in
68 Beiträge seit 2016
vor 3 Jahren

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.

16.806 Beiträge seit 2008
vor 3 Jahren

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!

M
MoaByter Themenstarter:in
68 Beiträge seit 2016
vor 3 Jahren

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.

4.931 Beiträge seit 2008
vor 3 Jahren

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).

M
MoaByter Themenstarter:in
68 Beiträge seit 2016
vor 3 Jahren

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.