Laden...

Daten erhalten von WebAPI, Berechnen und zurückschicken: Ist Threading hier sinnvoll?

Erstellt von tobi45f vor 5 Jahren Letzter Beitrag vor 5 Jahren 2.522 Views
T
tobi45f Themenstarter:in
59 Beiträge seit 2017
vor 5 Jahren
Daten erhalten von WebAPI, Berechnen und zurückschicken: Ist Threading hier sinnvoll?

Hallo zusammen,

ich bin Anfänger im Programmieren und habe mir für meine Masterarbeit als Elektrotechniker das programmieren beigebracht. Die Anwendung läuft auch wirklich gut auch wenn sie deutliche Problemchen hat. Diese Probleme würde ich gern beim zweiten Projekt das Bevorsteht umgehen.

Sowohl bei meinem ersten als auch bei dem neuen Projekt geht es um einen Berechnungsalgorithmus der auf ein Netzberechnungsprogramm zugreift. Ich lese/schreibe die Datenbank und habe mir so einen Prozess erstellt, der mein Problem löst/berechnet. Da ich sturktuell nicht erfahren bin habe ich bei meinem ersten Projekt das Problem, dass ich den Berechnungsablauf nicht abbrechen kann, wenn er einmal losgelaufen ist. Die GUI bekomme ich zwar über Befehle die ich mir ergoogelt habe aktualisiert aber ich vermute, dass es so ziemlich die unschönste Lösung ist.

Bei dem Projekt das ich jetzt vor der Nase habe ist der Ablauf der, dass ich über eine Schnittstelle (Web API) Daten erhalte, dann einen ähnlichen Berechnungsalgorithmus schreibe und die Ergebnisse dann wieder über diese Schnittstelle zurücksende.

Ich habe mich zum Threading eingelesen und frage mich, ob ich das wirklich brauche, da ja der Vorgang dennoch ein Prozess ist, der nicht parallel laufen kann. Schließlich brauche ich erst die Daten, wenn die da sind, dann muss berechnet werden. Auch bei der Berechnung kann nichts parallel laufen. Daten lesen -> Interpretieren -> Daten ändern rechnen und von vorn solange das Problem nicht gelöst ist. Ist die Berechnung fertig, dann senden.

Allerdings habe ich bei einem Beispiel des Web API gesehen, dass dort mit Threading gearbeitet wird und auch generell habe ich gelesen, dass Prozesse ab einer bestimmten Dauer als Thread ausgeführt werden sollen (Die Berechnung bei mir kann einige Minuten dauern). Nun bin ich etwas verwirrt, das hier das beste ist.

Mag mir jemand ein paar Tipps geben, wie ich das am besten anfasse? Ist Threading hier sinnvoll?

Grüße Tobias

3.003 Beiträge seit 2006
vor 5 Jahren

Ich habe mich zum Threading eingelesen und frage mich, ob ich das wirklich brauche, da ja der Vorgang dennoch ein Prozess ist, der nicht parallel laufen kann.

Nebenläufigkeit (Asynchronität) und Parallelität sind zwei Paar Schuhe, die du hier versehentlich gleich stellst.

Nebenläufigkeit ist in deinem Fall sinnvoll, weil du sicher nicht deine Berechnung auf demselben Thread ausführen möchtest, der die UI steuert. Parallelität ist dagegen nur sinnvoll bei parallelisierbaren Aufgaben.

Dein Einstieg ist hier: https://msdn.microsoft.com/de-de/library/hh191443(v=vs.120)

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

1.029 Beiträge seit 2010
vor 5 Jahren

Hi,

naja - Threading ist ohnehin involviert - grundsätzlich zum Abbrechen von solchen Operationen verwendet man "CancellationTokens". Hier ein Beispiel wie man das verwendet:
https://docs.microsoft.com/de-de/dotnet/standard/threading/cancellation-in-managed-threads

Ist recht simpel - im Endeffekt prüfst du während der Überprüfung immer wieder, ob die Berechnung abgebrochen werden soll und hörst dann auf zu rechnen (hier kann ggf. die OperationCanceledException nützlich sein, da du dann ja keinen Rückgabewert der Methode mehr hast).

Threading? Ist bei lang laufenden Sachen definitiv wichtig - ja - deine Berechnung braucht vielleicht nur einen Thread - der Rest der Anwendung sollte während der Berechnung dennoch bedienbar sein (und braucht dafür den UI-Thread) - allein um in der Lage zu sein einen entsprechenden "Abbrechen"-Button verwenden zu können. (und Fortschrittsbalken etc. pp.)
-> Application.DoEvents wie du es sicher verwendet hast sollte hier unbedingt nicht eingesetzt werden

Ich für meinen Teil werde stinkig, wenn mir Windows erzählt, dass eine Anwendung nicht mehr reagiert obwohl diese nur gerade rechnet - und das obwohl MultiThreading gerade unter .NET oft nicht mal so schwer ist.

LG

T
tobi45f Themenstarter:in
59 Beiträge seit 2017
vor 5 Jahren

Danke euch für die schnellen Antworten. Dann muss ich mich wohl erstmal in die Asynchrone Programmierung einlesen 😭 😁

Application.DoEvents sagt mir grade nichts und habe ich auch zum Glück nicht verwendet 🙂

Ich habe beim Start der Berechnung ein Fenster erzeugt, dass während des Prozesses läuft. In diesem Fenster dann ne Textbox die ich über

public void SetFortschritt(string msg)
        {
            Action act = () => { tbFortschritt.Text = msg; };
            tbFortschritt.Dispatcher.Invoke(act, System.Windows.Threading.DispatcherPriority.Render);
            this.UpdateLayout();
        }

aktualisiere. Der Aufruf der Methode ist dann im Berechnungsablauf eingebaut.

Ich hatte auch mal in diesem Fortschrittsfenster einen Button eingebaut und eine Abfrage, ob der Button gedrückt wurde oder nicht, in der Berechnung implementiert. Allerdings hat dieser nie reagiert. So wie ich das Ganze verstanden habe, eben wegen nur einem Thread. Der Text der GUI wird zwar erneuert aber die Abfrage des Buttons geht nicht, da der Prozess im Berechnungsablauf feststeckt?!

Grüße Tobias

T
tobi45f Themenstarter:in
59 Beiträge seit 2017
vor 5 Jahren

Hallo,

ich habe das mit dem Async jetzt mal getestet. Hierzu habe ich eine Main Klasse mit einem WPF Fenster und eine Klasse für die Berechnungen erstellt.

private async void MenuItem_Click(object sender, RoutedEventArgs e)
        {
            Task<string> teststring = Berechnungen.testAsync();
            string bla = await teststring;
            tb_1.Text = bla;
        }

In der statischen Klasse Berechnungen:

public static async Task<string> testAsync()
        {
            Thread.Sleep(3000);
            return "blablabl";
        }

Wenn ich das Thema richtig verstanden habe, dann dürfte der Code korrekt sein? Während der drei sekündigen Wartezeit kann ich aber das Fenster nicht bedienen, weder verschieben noch sonst was.
Außerdem zeigt er mir an, dass bei "testAsync()" der "await" Operator fehlt. Wenn ich statt des thread.sleep eine weitere Methode nutze, dann muss ich ja den Async/await Kram in jede weitere Ebene mitschleifen? Irgenwo wird doch zwangsläufig ein await fehlen?

public static async Task<string> testAsync()
        {
            await warte();
            return "blablabl";
        }

        public static async Task warte()
        {
            Thread.Sleep(3000);
        }

Hier habe ich jetzt das fehlende Await bei "warte()".

Wo ist mein Denkfehler?

Grüße Tobias

709 Beiträge seit 2008
vor 5 Jahren

Nimm zum Testen mal Task.Delay statt Thread.Sleep, denn das liefert auch einen Task zurück, wo man das await mit anwenden kann.

78 Beiträge seit 2016
vor 5 Jahren

http://dotnet-paderborn.azurewebsites.net/

T
tobi45f Themenstarter:in
59 Beiträge seit 2017
vor 5 Jahren

Nimm zum Testen mal
>
statt Thread.Sleep, denn das liefert auch einen Task zurück, wo man das await mit anwenden kann.

Ok das ist jetzt interessant. Ich habe es hier zusammen mit einem kurzen Teil des realen Vorgangs eingebaut.


public static async Task<string> testAsync()
        {
            await Task.Delay(3000);
            Netzzustandsanalyse();
            return "blablabl";
        }

Während der drei Sekunden aus Task.Delay kann ich das Fenster bewegen, im Anschluss die knapp 2 Sekunden nicht. Grade darauf kommt es aber an. Wenn ich die Optimierung damit programmiere, dann sind es nicht nur 2 Sekunden sondern 2-10 Minuten. Wie bekomme ich das dann hin? Doch einen Berechnungs-Thread?

1.029 Beiträge seit 2010
vor 5 Jahren

Hi,

das liegt daran, dass du keinen eigenen Task erstellst. Ein Beispiel für eine korrekte "Berechnungsmethode" (in meinem Beispiel das Gerippe für Primzahlen) würde das z.B. in einer Form so aussehen:


public partial class Form1 : Form
    {
        private CancellationTokenSource _cancellationTokenSource;

        public Form1()
        {
            InitializeComponent();
            btnCancel.Enabled = false;

            btnStart.Click += async (sender, args) =>
            {
                btnStart.Enabled = false;
                btnCancel.Enabled = true;
                _cancellationTokenSource = new CancellationTokenSource();

                try
                {
                    var data = await FetchDataFromWebApi(_cancellationTokenSource.Token);
                    var result = await ProcessDataFromWebApi(data, _cancellationTokenSource.Token);
                }
                catch (OperationCanceledException e)
                {
                    if (e.CancellationToken != _cancellationTokenSource.Token) // ignore
                        throw;
                }
                finally
                {
                    btnStart.Enabled = true;
                    btnCancel.Enabled = false;
                }
            };

            btnCancel.Click += (sender, args) => { _cancellationTokenSource.Cancel(); };
        }

        public async Task<string> FetchDataFromWebApi(CancellationToken token)
        {
            using (var client = new HttpClient())
            {
                using (var response = await client.GetAsync("https://www.google.de", token))
                {
                    using (var content = response.Content)
                    {
                        string result = await content.ReadAsStringAsync();
                        return result;
                    }
                }
            }
        }

        public Task<int> ProcessDataFromWebApi(string data, CancellationToken token)
        {
            return Task<int>.Factory.StartNew(() =>
            {
                for (long i = 0; i <= 100000; i++)
                {
                    for (long j = 2; j < i; j++) // you actually only need to check up to sqrt(i)
                    {
                        if (token.IsCancellationRequested)
                            throw new OperationCanceledException(token);

                        if (i % j == 0) // you don't need the first condition
                        {
                            break;
                        }
                    }
                }
                return 0;
            }, token);
        }
    }

LG

T
tobi45f Themenstarter:in
59 Beiträge seit 2017
vor 5 Jahren

Hallo,

ich habe jetzt einen Task für meine Methode erstellt


private async void MenuItem_Click(object sender, RoutedEventArgs e)
        {
            tb_1.Text = "1 2 3 ";
            Task<string> RechenTask = Berechnungen.NetzzustandsanalyseAsync();
            tb_1.Text += "meanwhile";
            string erg = await RechenTask;
            tb_1.Text += erg;
            tb_1.Text += "7 8 9 ";
        }
public static async Task<string> NetzzustandsanalyseAsync()
        {
            // langandauernder Prozess
            return (" fertig ");
        }

Das Ergebnis ist mit 1 2 3 meanwhile fertig 7 8 9 zwar korrekt, aber das Fenster friert dennoch ein. Ganz korrekt kann es auch nicht sein, wenn ich in einer Methode mit der Rückgabe Task<string> nur einen string zurückgebe.
Weiterhin ist die Methode Netzzustandsanalyse eigentlich auch gar nicht asynchron? Nur der Aufruf soll ja asynchron sein, deswegen ist ja das clickEvent mit async gekennzeichnet? Wenn ich async aus dem Methodenkopf entferne, dann meckert der Kompiler auch mit dem Rückgabewert.
Irgendwie habe ich noch einen Denkfehler bei der ganzen Thematik drin. Leider konnte ich mir das anhand des Beispielcodes nicht erklären. 🤔

1.029 Beiträge seit 2010
vor 5 Jahren

Hi,

naja - wenn du "await" verwenden möchtest - muss die Methode eben "async" gekennzeichnet sein.

Was deinen Beispielcode angeht: Ich sehe nach wie vor keinen eigenen Task. (Ich meine damit das Task.Run - hiermit wird nämlich letztlich ein anderer Thread als jener der UI verwendet, was eben die Parallelität ermöglicht)

Das Thema ist: async heißt nicht parallel - sondern vielmehr nebenläufig - schau dir z.B. mal folgenden Thread of StackOverflow an:
https://stackoverflow.com/questions/24953808/write-your-own-async-method

LG

Edit:
Async-Await ist letztendlich Syntax-Zucker für ein Gerüst mit TaskCompletion, es wird dafür verwendet im Optimalfall auf ein Ergebnis zu warten, für dessen Dauer das Programm nicht wirklich selbst verantwortlich ist - siehe z.B. http://blog.stephencleary.com/2012/02/async-and-await.html. Async wird somit für Sachen verwendet, die mit IO zusammenhängen. (Festplattenzugriffe, WebRequests, etc.) - das ist auch der Grund, aus dem meine Methode "ProcessData..." nicht mit async gekennzeichnet ist - es gibt keinen IO-Vorgang auf den ich warte - sondern nur eine Berechnung, die die CPU ausführt - deshalb ist das ein richtiger "Task".

Edit2 für etwas zusätzliche Lektüre:

  1. Multithreading in C#: http://www.codeplanet.eu/tutorials/csharp/64-multithreading-in-csharp.html (die eigentliche Basis)
  2. Async await: https://docs.microsoft.com/de-de/dotnet/csharp/programming-guide/concepts/async/ (und warum es das gibt)
T
tobi45f Themenstarter:in
59 Beiträge seit 2017
vor 5 Jahren

Hi,

@Taipi88, deinen ersten Link habe ich mir jetzt nicht durchgelesen, da ich schon in zwei anderen Büchern den Threading Teil gelesen habe.
Der zweite Link wurde ja bereits gepostet, habe ich durcharbeitet aber ist für mich irgendwie nicht 100% nachvollziehbar. Mir fehlt da was. Task.Run wird zwar angesprochen, aber die Umsetzung damit ist keinem Beispiel zu entnehmen.

Ich habe folgende Lösung erstellt:

Mein WPF Fenster, also GUI Klasse


public partial class MainWindow : Window
    {
private void MenuItem_Click(object sender, RoutedEventArgs e)
        {
            Berechnungen.GUI = this;
            Thread th = new Thread(Berechnungen.NebenlaufigkeitTest);
            Console.WriteLine("Start");
            th.Start();            
        }
public void testTextbox(string test)
        {
            this.Dispatcher.Invoke(delegate
            {
                tb_1.Text = test;
            });
        }
public bool Btn_Abbruch()
        {
            return bool_Abbruch;
        }
private void Abbruch_Click(object sender, RoutedEventArgs e)
        {
            bool_Abbruch= true;
        }
    }

und meine statische Berechnungs-Klasse:

static class Berechnungen
    {
        private static MainWindow gui;

        public static MainWindow GU // Get/Set

public static void NebenlaufigkeitTest()
        {
            for (int i = 5; i>0; i--)
            {

if(GUI.Btn_Abbruch())
                {
                    Console.WriteLine("wird abgebrochen");
                    break;
                }

                Simulate(); // Der Zugriff auf das Netzberechnungsprogramm mit hoher Laufzeit

                GUI.testTextbox("blabla" + i);
            }
        }
}

Ist dagegen irgendwas einzuwenden, was ich grade übersehe? Das Fenster friert nicht an, die Anzeige wird aktualisiert, ich kann mit einem Button abbrechen, erscheint mir eigentlich richtig?

Grüße Tobias

1.029 Beiträge seit 2010
vor 5 Jahren

Hi,

das Thread.Start ist prinzipiell das "alte" Äquivalent zum "Task.Run".

In der Praxis wird es für deine Zwecke denkbar wenig Unterschiede geben - dennoch solltest du Task.Run (mit der Option LongRunning) verwenden.

Hintergrund:
.NET holt sich vom Betriebssystem ohnehin im Standard mehrere Threads, die nichts tun außer darauf zu warten, dass diese benutzt werden. Indem du Task.Run verwendest - holst du dir eben keinen komplett neuen Thread - sondern einen aus besagtem ThreadPool. Achtung: Das gilt hier zwar nicht, weil du ja LongRunning verwenden sollst - dennoch ist es eigentlich deutlich leichter einen Task zu handeln (auch mit Blick auf die Methode Task.Wait) - aber so generell würde ich an deiner Stelle mit Tasks arbeiten es sei denn du hättest Anforderungen, die direkten Zugriff auf Threads bedingen.

Zudem kannst du bei Verwendung eines Tasks auch einfacher mit dem Ergebnis weiterrechnen, wenn du z.B. Methoden à la ContinueWith verwendest. Selbes gilt für async/await... Alles Komfortfunktionen, die Threads abstrahieren sollen.

Des Weiteren solltest du wirklich mit dem von mir gezeigten CancellationToken arbeiten - ja deine Lösung funktioniert vll ähnlich gut - lässt sich allerdings nicht in dem absichtlich von mir gezeigten Code zur Abholung von Daten aus dem Internet mitverwenden. Zudem ist es eigentlich immer gescheit sich an existierenden Standards zu orientieren...

LG

T
tobi45f Themenstarter:in
59 Beiträge seit 2017
vor 5 Jahren

Alles klar, danke dafür. Dann muss ich mich wohl in Richtung der CancellationToken einlesen und danach versuchen, dein Beispiel zu verstehen.

Grüße Tobias

T
tobi45f Themenstarter:in
59 Beiträge seit 2017
vor 5 Jahren

Hi,

so auf ein neues. Wenn ich das mit den CancellingToken und den Tasks richtig verstehe, dann müsste dies richtig sein (funktionieren tut es zumindest):

WPF GUI Klasse


public partial class MainWindow : Window
    {
        Task<bool> t;

        CancellationTokenSource tokenSource;   
     
        private void MenuItem_Click(object sender, RoutedEventArgs e)
        {
            Berechnungen.GUI = this;

            tokenSource = new CancellationTokenSource();
            var token = tokenSource.Token;

            t = Task<bool>.Run(() => Berechnungen.NebenlaufigkeitTest(token, tokenSource),token);
        }
private void abbruch_Click(object sender, RoutedEventArgs e)
        {
            tokenSource.Cancel();            
        }

Berechnungen Klasse


public static bool NebenlaufigkeitTest(CancellationToken ct, CancellationTokenSource ts)
        {
            //Abfrage ct vor Start:
            if (ct.IsCancellationRequested == true)
            {
                Console.WriteLine("Task was cancelled before it got started.");
                ct.ThrowIfCancellationRequested();
            }

            Console.WriteLine("start");

            try
            {
                for (int i = 0; i < 10; i++)
                {
                    Thread.Sleep(3000);
                    Simulate(); // Langer Prozess
                    Console.WriteLine("Berechnung fertig " + i);
                    GUI.testTextbox(i);

                    //Abfrage im Berechnungsvorgang
                    if (ct.IsCancellationRequested == true)
                    {
                        Console.WriteLine("Task wurde in der laufenden Berechnung abgebrochen");
                        ct.ThrowIfCancellationRequested();
                    }
                }
            }
            catch (OperationCanceledException)
            {
                Console.WriteLine("exeption Code");
                throw;
            }
            finally
            {
                Console.WriteLine("finally Code");
                ts.Dispose();
            }
            return true;
        }

Ist das korrekt?
Danke für deine/eure Hilfe!
Grüße Tobias

edit: oh ich habe beim Task die Option LongRunning vergessen.
Wäre dann:

            t = Task<bool>.Run(() => Berechnungen.NebenlaufigkeitTest(token, tokenSource),token, TaskCreationOptions.LongRunning);
1.029 Beiträge seit 2010
vor 5 Jahren

Hi,

was mir noch auffällt:

a) Wenn du irgendeiner Berechnungs-Klasse eine Referenz auf deine Form geben musst - machst du etwas falsch - hier solltest du ggf. Kommunikation über Events vorziehen
siehe:


Berechnungen.GUI = this;

oder auch


GUI.testTextbox(i);

b) Abfragen à la myBoolValue == true sind eher schlechter Stil (siehe [Tipp] Anfängerhinweis == true / == false)
c) Die OperationCanceledException solltest du nicht in der selben Methode fangen, in der du diese auslöst - vielmehr dort, wo der Task gestartet wird. (Das ist die UI, und die soll ja letztlich auch eine Meldung anzeigen)
d) die CancellationTokenSource würde ich aus der Berechnungsmethode rauslassen - die braucht eigentlich nur den Token.

Ansonsten sollte das in Ordnung sein.

LG

T
tobi45f Themenstarter:in
59 Beiträge seit 2017
vor 5 Jahren

Hi,

a) Wie gesagt, ich habe mir das selbst beigebracht. Dass hier und ad stilistische Lücken sind, ist nicht auszuschließen 🙂 Ich werde mal recherchieren 👍

b) Ja ist richtig. Ich habe diese Auszüge aus einem Beispiel kopiert und mir da keine weiteren Gedanken drüber gemacht, obwohl ich das bei selbstgeschriebenem Code richtig mache. Aber du hast natürlich recht.

c) Das habe ich mir auch gedacht, aber ich hatte immer einen Fehler, wenn ich es in der Methode, in der ich es aufrufe, behandele. Ich denke das Problem ist bei dem Dispose. Wenn ich das weglasse, dann geht es:

private void MenuItem_Click(object sender, RoutedEventArgs e)
        {
            Berechnungen.GUI = this;

            tokenSource = new CancellationTokenSource();
            var token = tokenSource.Token;
            try
            {
                t = Task<bool>.Run(() => Berechnungen.NebenlaufigkeitTest(token), token);
                tb_1.IsEnabled = false;
            }
            catch (OperationCanceledException)
            {
                Console.WriteLine("exeption Code");
                throw;
            }
            finally
            {
                Console.WriteLine("finally Code");
                //tokenSource.Dispose();
            }

        }

        private void abbruch_Click(object sender, RoutedEventArgs e)
        {
            tokenSource.Cancel();            
        }

Ist das Dispose drin, dann zeigt er mir einen Fehler an beim abbruch_Click - tokenSource.Cancel(), mit der Angabe, dass die CancellationTokenSource gelöscht wurde!? Brauche ich das Dispose? Bei allen Beispielen war es dabei?

d) klar, war nur drin, wegen des Dispose. Nun ist es raus. 🙂

Ich danke dir!

Gruß Tobias

1.029 Beiträge seit 2010
vor 5 Jahren

Hi,

Zu a)
Das ist weniger ein stilistischer Fehler als einer aus der Architektur, mit dem du dir das Leben sehr schwer machen kannst. Zum einen verzahnst du deinen (eigentlich) unabhängigen Berechnungscode unnötigerweise mit einer bestimmten UI - zum anderen wird dadurch der Code am Ende schwieriger zu überblicken. (Beides ist auf Dauer sehr unangenehm)

Als Lesetipps:
3-Schichten-Architektur: [Artikel] Drei-Schichten-Architektur
Ereignisse: [FAQ] Eigenen Event definieren / Information zu Events (Ereignis/Ereignisse)

Zu c)
Wenn du drüber nachdenkst recht logisch - hatte in meinem Beispiel bewusst "await" eingesetzt. Das Thema ist: In deinem Code läuft das finally durch, obwohl die Berechnung noch lange nicht durch ist, womit ein Dispose natürlich knallt. Der einfachste Weg ist somit das await zu verwenden, damit dein finally erst läuft, wenn der Task beendet oder abgebrochen wurde. (Mit dem await wird der darauf folgende Code erst ausgeführt, wenn der Task durch is)

LG

T
tobi45f Themenstarter:in
59 Beiträge seit 2017
vor 5 Jahren

Hi,

mein Problem mit der Asynchronie ist einfach, dass ich kein Beispiel finde, dass das vereint, was ich suche. Und wenn ich mir mehrere Quellen raussuche, damit ich alles habe, dann passt es irgendwie nicht zusammen.

Sowohl in deinem Beispiel als auch bei der Beschreibung der Asynchronen Programmierung wird kein Task.Run verwendet. Ich kann also nur raten, wie die Token und das Run zusammenspielt.

Weiterhin möchte ich ja, dass meine Berechnungsmethode synchron abgearbeitet wird, allerdings asynchron zur GUI aufgerufen wird. Der Methodenkopf ist grün unterstrichen, weil ein bool x = await Methodenname() erwartet wird. Mache ich diese Zeile rein, dann läuft es in einer Endlosschleife. Ohne läuft es, wie es soll, nur dass der Kompiler halt meckert.

Also hier mein Stand:

GUI

CancellationTokenSource tokenSource;        
        private async void MenuItem_Click(object sender, RoutedEventArgs e)
        {
            //Berechnungen.GUI = this;
            Berechnungen.WerteAnTextBox += new EventHandler<int>(SchreibeFortschritt);

            tokenSource = new CancellationTokenSource();
            var token = tokenSource.Token;
            try
            {
                bool t = await Task.Run(() =>  Berechnungen.NebenlaufigkeitTestAsync(token), token);
                tb_1.IsEnabled = false;
            }
            catch (OperationCanceledException)
            {
                Console.WriteLine("exeption Code");
            }
            finally
            {
                Console.WriteLine("finally Code");
                tokenSource.Dispose();
            }
        }
        private void SchreibeFortschritt(object sender, int i)
        {
            //If(sender is Berechnungen) geht aber nicht weil sender=null!?
            this.Dispatcher.Invoke(delegate
            {
                tb_1.Text = i.ToString();
            });
        }

        private void abbruch_Click(object sender, RoutedEventArgs e)
        {
            tokenSource.Cancel();            
        }

Berechnungsklasse

public static event EventHandler<int> WerteAnTextBox;
        public static async Task<bool> NebenlaufigkeitTestAsync(CancellationToken ct)
        {           
            //Abfrage ct vor Start:
            if (ct.IsCancellationRequested)
            {
                Console.WriteLine("Task was cancelled before it got started.");
                ct.ThrowIfCancellationRequested();
            }

            Console.WriteLine("start");

            for (int i = 0; i < 10; i++)
            {
                SimulateCon("LF");
                Console.WriteLine("Berechnung fertig " + i);

                WerteAnTextBox(null, i); // Wer ist hier object sender? this geht nicht

                //Abfrage im Berechnungsvorgang
                if (ct.IsCancellationRequested)
                {
                    Console.WriteLine("Task wurde in der laufenden Berechnung abgebrochen");
                    ct.ThrowIfCancellationRequested();
                }
            }
            //bool x = await NebenlaufigkeitTestAsync(ct); Wenn drin, dann Endlosschleife
            return true;
        }

Danke für deine Hilfe!

Gruß Tobias

1.029 Beiträge seit 2010
vor 5 Jahren

Hi,

mein Code enthält ein Task.Factory.StartNew, das im Wesentlichen dem Task.Run entspricht - nur eben mit der Option LongRunning versehen werden kann - du hast es nur nicht gefunden, weil es in der Berechnungsmethode selbst drin war. (Wobei es letztendlich keinen Unterschied macht, ob es wie mir in der Methode, oder außerhalb beim Aufruf verwendet wird)

Zur Verdeutlichung:

public Task<int> ProcessDataFromWebApi(string data, CancellationToken token)
        {
            return Task<int>.Factory.StartNew(() => // HIER ist das "Task.Run"
            {
                for (long i = 0; i <= 100000; i++) // Bedeutungslos, verursacht nur Laufzeit
                {
                    for (long j = 2; j < i; j++) // Bedeutungslos, verursacht nur Laufzeit
                    {
                        if (token.IsCancellationRequested) // HIER wird geprüft ob abgebrochen werden soll
                            throw new OperationCanceledException(token); // Hier wird abgrebrochen

                        if (i % j == 0) // Bedeutungslos, verursacht nur Laufzeit
                        {
                            break; // Bedeutungslos, verursacht nur Laufzeit
                        }
                    }
                }
                return 0;
            }, token);
        }

(Bei allem wo bedeutungslos nebendran steht - handelt es sich um die Berechnung von Primzahlen, damit die Methode eine gewisse Laufzeit hat - habe ich hier verwendet, damit ich for-schleifen habe um regelmäßig auf "token.CancelationRequested" prüfen zu können.

Was deinen Code angeht:
Wenn du in der Berechnungsmethode kein Task.Run ausführst und auch kein "await" verwendest - dann sollte jene Methode weder ein "Task<bool>" als Rückgabewert besitzen - noch mit async markiert sein.

Des Weiteren: Bitte verzichte auf static. Es gibt selten gute Gründe dafür. Und Events und eine Berechnungsmethode gehören definitiv nicht dazu. (Gut möglich, dass deine Probleme damit zusammenhängen)

Wenn du das Task.Run / Task.Factory.StartNew aus der Berechnungsmethode rauslassen möchtest - hier als Beispiel meine entsprechend umformulierte Methode:


public int ProcessDataFromWebApiSync(string data, CancellationToken token)
        {
            for (long i = 0; i <= 100000; i++) // bedeutungslos
            {
                for (long j = 2; j < i; j++) // bedeutungslos
                {
                    if (token.IsCancellationRequested) // prüfen ob abgebrochen werden soll
                        throw new OperationCanceledException(token); // abbbrechen

                    if (i % j == 0) // bedeutunglos
                        break; // bedeutungslos
                }
            }
            return 0; // ergebnis zurückgeben
        }

Der korrekte Aufruf mit LongRunning sieht dann folgendermaßen aus:


try
                {
                    var token = _cancellationTokenSource.Token;
                    var calc = new Calculation();
                    var data = await calc.FetchDataFromWebApi(token);
                    var result = await Task<int>.Factory.StartNew(() => calc.ProcessDataFromWebApiSync(data, token), token, TaskCreationOptions.LongRunning, TaskScheduler.Default); // <-- das da ist der wichtige Aufruf!
                }
                catch (OperationCanceledException e)
                {
                    if (e.CancellationToken != _cancellationTokenSource.Token) // ignore
                        throw;
                }
                finally
                {
                    _cancellationTokenSource.Dispose();
                    btnStart.Enabled = true;
                    btnCancel.Enabled = false;
                }

LG

T
tobi45f Themenstarter:in
59 Beiträge seit 2017
vor 5 Jahren

Hi,

soweit alles umgesetzt und soweit alles gut, nur dass jetzt wieder was anderes nicht geht. Er meint auf einmal, ich hätte OperationCancellingException nicht behandelt.

CancellationTokenSource tokenSource;        
        private async void MenuItem_Click(object sender, RoutedEventArgs e)
        {
//Meine Instanz von Berechnung habe ich Optimierung genannt. Die Klasse ist nicht mehr statisch.
            optimierung.WerteAnTextBox += new EventHandler<int>(SchreibeFortschritt);

            tokenSource = new CancellationTokenSource();
            var token = tokenSource.Token;
            try
            {
                bool res = await Task<bool>.Factory.StartNew(() => optimierung.NebenlaufigkeitTest(token), token, TaskCreationOptions.LongRunning,TaskScheduler.Default);
                tb_1.IsEnabled = false;
            }
            catch (OperationCanceledException)
            {
                Console.WriteLine("exeption Code");
            }
            finally
            {
                Console.WriteLine("finally Code");
                tokenSource.Dispose();
            }
        }
        private void SchreibeFortschritt(object sender, int i)
        {
            if(sender is Berechnungen)
            {
                this.Dispatcher.Invoke(delegate
                {
                    tb_1.Text = i.ToString();
                });
            }            
        }

        private void abbruch_Click(object sender, RoutedEventArgs e)
        {
            tokenSource.Cancel();            
        }
public event EventHandler<int> WerteAnTextBox;
        public bool NebenlaufigkeitTest(CancellationToken ct)
        {           
            //Abfrage ct vor Start:
            if (ct.IsCancellationRequested)
            {
                Console.WriteLine("Task was cancelled before it got started.");
                ct.ThrowIfCancellationRequested();
            }

            Console.WriteLine("start");

            for (int i = 0; i < 10; i++)
            {
                SimulateCon("LF");
                Console.WriteLine("Berechnung fertig " + i);

                WerteAnTextBox(this, i);

                //Abfrage im Berechnungsvorgang
                if (ct.IsCancellationRequested)
                {
                    Console.WriteLine("Task wurde in der laufenden Berechnung abgebrochen");
                    ct.ThrowIfCancellationRequested(); // Eine Ausnahme vom Typ "System.OperationCanceledException" ist in mscorlib.dll aufgetreten, doch wurde diese im Benutzercode nicht verarbeitet.
                    // Ist doch im Aufruf der Methode erfolgt, du du es am anfang dieses Threads beschrieben hattest?!
                }
            }
            return true;
        }

Danke, Grüße Tobias

G
74 Beiträge seit 2018
vor 5 Jahren

Wo ist den OperationCanceledException definiert ? Ist sie vom Typ Exception ?

müßte es nicht nicht

catch (Exception e) lauten ?

1.029 Beiträge seit 2010
vor 5 Jahren

Hi,

zum Fehler - du musst es im Release-Modus ausführen um zu sehen, wie es sich ohne angehefteten Debugger verhält - er würde die Exception schon noch fangen. Zumindest tut er das bei mir absolut problemlos. Wenn der Debugger dranhängt einfach noch mal F5 drücken - dann geht's auch weiter.

LG

T
tobi45f Themenstarter:in
59 Beiträge seit 2017
vor 5 Jahren

Hi,

ich nehme an, der Link von Th69 war für Glowhollow gedacht?

Ja stimmt, wenn man einfach weiterdrückt, dann fängt er die Exceptio noch. Vielen Dank für die Beretung, Hilfe und Geduld Taipi! Jetzt sollte die Struktur stehen und es kann los gehen 😃

Grüße Tobias