Laden...

await/async Rückgabe des Results an den Aufrufer innerhalb Loop

Erstellt von BlackMatrix vor 10 Jahren Letzter Beitrag vor 10 Jahren 2.214 Views
B
BlackMatrix Themenstarter:in
218 Beiträge seit 2012
vor 10 Jahren
await/async Rückgabe des Results an den Aufrufer innerhalb Loop

Hey,

mir ist kein passenderer Titel eingefallen.

Ich möchte gerne das async/await Pattern verwenden um mir folgende Möglichkeit zu geben. Ich habe einen Task, der eine Schleife ausführt. Jeder Schleifendurchlauf hat in etwa eine Dauer von 3-4 Sekunden und liefert ein Resultat, welches ich dem Aufrufer unmittelbar zurückgeben möchte. Die Schleife muss synchron hintereinander ablaufen und die ganze Schleife soll auch nur in einem Task ausgeführt werden.


Task.Run(() =>
                {
                    for (int i = 0; i < 10; i++)
                    {
                        Thread.Sleep(3500);
                        // Rückgabe einer List<string> an den Aufrufer
                        // weitere Bearbeitung der Schleife
                    }
                });
        

Ist diese ein Fall für die Progress Overload, indem ich bei jedem Schleifendurchlauf das Resultat mitliefere oder gibt es dafür etwas geeigneteres?

Viele Grüße

1.346 Beiträge seit 2008
vor 10 Jahren

Guck dir mal IObserver und IObservable an.

LG pdelvo

16.806 Beiträge seit 2008
vor 10 Jahren

Du kannst ja nicht mitten in einer Schleife ein return ausführen und dann weiter arbeiten wollen.
Da hast Du den async/await-Pattern falsch verstanden.

Wenn Du das Ergebnis weiterverarbeiten willst wäre TPL Pipelines was für Dich.
Wenns nur um die Rückgabe geht dann _eigentlich _über Events (je nach Gesamtarchitektur).

B
BlackMatrix Themenstarter:in
218 Beiträge seit 2012
vor 10 Jahren

Wie ist folgender Code zu bewerten?


static void Main(string[] args)
        {
            Do();
            Console.ReadKey();
        }

        private static async void Do()
        {
            var progress = new Progress<List<string>>(Update);

            await GetStringsAsync("test", progress);
        }

        private static void Update(List<string> strings)
        {
                // Teilergebnisse abarbeiten
        }

        async static Task GetStringsAsync(string searchWord, IProgress<List<string>> progress)
        {            
            await Task.Run(() =>
            {
                for (int i = 0; i < 10; i++)
                {
                    Thread.Sleep(3500);// Simuliere Arbeit
                    if (progress != null)
                        progress.Report(new List<string> { i.ToString() });// Resultat simulieren
                }
            });
        }      
16.806 Beiträge seit 2008
vor 10 Jahren

Hat mit dem Sinn von async/await nicht wirklich zutun.
Könnte genausogut, wenn nicht sogar besser ohne async laufen, indem einfach nur der Task verwendet wird.

Wie gesagt; ich fürchte Du hast den Sinn von async/await nicht verstanden.
Das hier ist nichts anderes als das simple Auslagern einer Tätigkeit in einen Hintergrund-Task.
In Zusammenhang mit dem Update() wären wir dann wohl wirklich beim Thema Pipelining.

Die Frage ist aber auch: warum willst Du async/await verwenden?
95% verwenden es wohl aus dem Hype-Grund; nicht weil sie's wirklich brauchen - geschweige denn, dass sie verstehen, was das überhaupt macht. Ich zB vermeide es so gut wie's geht.
Dahinter steckt ja nichts anderes als CompilerMagic und kann genausogut, wenn nicht sogar deutlich lesbarer, einfach via Tasks umgesetzt werden.
Hinzu kommt auch immer ein gewisser Overhead; sodass es sich oft nicht lohnt.

49.485 Beiträge seit 2005
vor 10 Jahren

Hallo BlackMatrix,

außerdem musst dir darüber im Klaren sein, was in der MSDN der Progress-Klasse steht:

Jeder Handler, der vom Konstruktor bereitgestellten oder die Ereignishandler, die dem ProgressChanged-Ereignis registriert sind, werden durch eine aufgezeichnete SynchronizationContext-Instanz aufgerufen, wenn die Instanz erstellt wird. Wenn kein aktuelles SynchronizationContext zum Zeitpunkt der Konstruktion gibt, werden die Rückrufe auf [den] ThreadPool [delegiert].

Die Verarbeitung der Update-Methode wird also im Normalfall entweder im GUI-Thread laufen oder in irgendeinem gerade freien ThreadPool-Thread.

herbivore

B
BlackMatrix Themenstarter:in
218 Beiträge seit 2012
vor 10 Jahren

Bisher habe ich das ganze Szenario so gehandhabt, das ich die komplette Aufgabe in einen einzelnen Task ausgelagert habe und auch erst nach Ausführung das komplette Ergebnis zurückgeliefert habe. Eigentlich könnte ich aber das ganze so umbauen, das jedes Mal, wenn eben ein Schleifendurchlauf ausgeführt worden ist, der Aufrufer informiert wird und daraufhin mit diesem Resultat (hier: List<string>) jeweils eine weitere lang laufende Aktion (je string ein Task) ausgeführt werden kann. Erst danach wird die GUI jeweils nach Vollendung eines solchen ContinueEinzelTasks aktualisiert.

Nun klingt TPL Pipelining schon sehr nach diesem Konzept, welches ich suche.

16.806 Beiträge seit 2008
vor 10 Jahren

Eigentlich könnte ich aber das ganze so umbauen, das jedes Mal, wenn eben ein Schleifendurchlauf ausgeführt worden ist,

Du könntest einen Enumerator bauen, der so funktioniert und mit yield arbeiten. Wär halt schon ein schmutziges Missbrauchen dieses Patterns :evil:
Und gewonnen in Sachen Paralellität hast dann trotzdem nichts.
Aber das ist trotzdem weit weit weg von Async/Await.

Insgesamt denke ich, Du solltest Dir nochmal anschauen, wie Multi-Threading funktioniert.
Hört sich schon so an, als ob hier das Basis-Wissen fehlt.

W
955 Beiträge seit 2010
vor 10 Jahren

...das klingt eher nach yield return/yield break und Enumerator.

B
BlackMatrix Themenstarter:in
218 Beiträge seit 2012
vor 10 Jahren

Eigentlich könnte ich aber das ganze so umbauen, das jedes Mal, wenn eben ein Schleifendurchlauf ausgeführt worden ist,
Du könntest einen Enumerator bauen, der so funktioniert und mit yield arbeiten.

An yield hatte ich eben auch schon gedacht, aber da war tatsächlich noch kein Wissen vorhanden wie man das einsetzt.

Und gewonnen in Sachen Paralellität hast dann trotzdem nichts.

Natürlich, ich kann bereits die Ausführung der nächsten darauf aufbauenden lang andauerenden Tasks ausführen, sobald der eine Haupttask ein Resultat geliefert hat.

Haupttask (arbeitet weiter) --> Teilergebnis (List<string>) --> pro String ein Task --> Update GUI

16.806 Beiträge seit 2008
vor 10 Jahren

Nein.

Wenn Du ein Enumerator baust und in in einer Schleife in Thread A drauf zu greifst und via yield etwas returns, dann blockiert das so lange, bis das nächste Ergebnis aus dem Enumerator aus Thread B ermittelt wird.
Du willst aber das Blockieren doch vermeiden....

Du könntest natürlich im Enumerator selbst einen Task verwenden, der eine BlockingCollection füllt und daraus das yield lebt; sehe aber auch hier keine entgültige Lösung Deines Problems, da auch hier die BC solange blockt, bis das nächste Elemente da ist.
"Zwischendurch" was returnen ist eben auf diese Weise nicht möglich. Das ist halt Pipelining / Events.

B
BlackMatrix Themenstarter:in
218 Beiträge seit 2012
vor 10 Jahren

Haupttask (arbeitet weiter) --> Teilergebnis (List<string>) --> pro String ein Task --> arbeitet --> Update GUI

16.806 Beiträge seit 2008
vor 10 Jahren

Ja und genau das ist unsauber. Wir drehen uns im Kreis.

Sauber wäre hier viel eher:
GUI -> Business-Logik mit innener Abarbeitung (Task) -> Bereitstellung eines Events bei Update durch die BL -> GUI Aktualisierung durch den Event.

Hier braucht man nur einen Task; bei Dir zig....

Im Endeffekt ist das [FAQ] Controls von Thread aktualisieren lassen (Control.Invoke/Dispatcher.Invoke)

B
BlackMatrix Themenstarter:in
218 Beiträge seit 2012
vor 10 Jahren

Da stimmt ich dir zu. War ich wohl im async/await Wahn 😉

Ich danke.