C# Exception aus Fire and Forget Task behandeln?

2 Antworten

Der Code wirkt wirr - jedenfalls nicht so, wie man es machen sollte.
Ich kenne Selenium nicht, aber das, was Du da hast, ist nicht nicht wirklich async, sondern eher sowas verpfuschtes ähnliches wie async.
Tu dir einen Gefallen und arbeite dich ausführlich (!!!) in die Funktionsweise von await und den Tasks ein, bis Du begriffen hast, was im Hintergrund *wirklich* passiert, das ist nämlich deutlich mehr, als das, was Du im Code siehst.

Um die Frage zu beantworten:

Exceptions in einem Task sollte man nur im Task oder beim Aufruf fangen. Da dein Task ein Endlos-Task ist, fällt letzteres ins Wasser, dir bleibt also nur die Option, im Task selber (also in der while-Schleife) zu fangen.

Aber was soll der Code eigentlich tun?

Du hast zwei Queues, die erst Dequeuen, um anschließend wieder zu Enqueuen? Das klingt doppelt gemoppelt und es ist nicht mal sichergestellt, dass die beiden Objekte aus beiden Queues auch immer die selben Paare bilden - sollte das notwendig sein.

Oder Du machst es synchron und startest einen eigenen Thread oder einen Task (mit LongRunning gekennzeichnet), musst dich dann aber auch um die synchronisierung zurück in den UI-Thread kümmern, was async dir abnehmen kann.

Was mir - ohne zu wissen, was der Code tun soll - auffällt:

  • Wie gesagt, das ist kein richtiges async, ohne das "await Task.Delay()) sollte VisualStudio dich auch darauf hinweisen
  • Nutze kein ContinueWith, außer in absoluten Ausnahmefällen, wenn Du weißt, was Du tust
  • Wenn Du das aus einem UI-Thread heraus startest, blockiert die UI - vermutlich ewig. Theoretisch wäre das sogar ein Deadlock, mir ist nicht klar, warum nicht
  • Weder das Observe, noch das Delay brechen ab, wenn der CancellationToken triggert, sind aber beide asynchron
  • Die Reihenfolge der beiden Enqueue und Dequeue sollten nicht wichtig sein, verhindert aber eine Exception, wenn eine der beiden Queues leer ist. Es sollten beide Queues überprüft werden
  • Asynchrone Methoden sollten mit "Async" enden

Außerdem:

Mehr fällt mir spontan nicht ein.

Woher ich das weiß:Berufserfahrung – C#.NET Senior Softwareentwickler

payed99170 
Beitragsersteller
 21.02.2022, 23:18

Oh je das war aber viel :D

Also pass auf:

Mit Selenium kann man Browser steuern. Ich will also ne Website automatisiert bedienen. Es gibt mehrere unabhängige Dinge, die da von Zeit zu Zeit gedrückt werden sollen. In der objectsQueue sind dann z.B. 5 Elemente. Ich hab aber nur z.B. 2 Observer = 2 Browser.

Die objectsQueue soll abgearbeitet werden, und sobald es einmal durch ist wieder hinten angestellt werden.

Dafür hab ich jetzt hier die 2 Observer. Und sobald einer davon "fertig" ist, soll er das erste Element in der Queue nehmen, was damit machen und wieder hinten anstellen. Deswegen 2 Queues.

Ich blocke den UI Thread nicht, weil der Observe Task ebenfalls mit .ContinueWith aufgerufen wird :D

            observerTask = ...Observe(..., _cts.Token);
            _ = observerTask.ContinueWith(x =>
            {
                //Restart
            }, TaskContinuationOptions.OnlyOnFaulted);

Über die UI drücke ich also auf nen Knopf und dann sollen x Browser aufgehen, die im Hintergrund permanent das Zeug machen. Es gibt einen Task, der hier die Observe Methode aufruft (wie hier im Kommentar oben gezeigt, auch ohne await).

In diesem Task (Code aus der Frage) werden dann die ganzen Observer (Browser) verwaltet. Hier brauch ich auch Tasks auf deren Beendigung ich nicht warte. Die sollen ja parallel laufen und sich, sobald fertig, ein neues Element aus der objectsQueue ziehen. Aber: Dabei kann nen Fehler auftreten, z.B. wenn der Browser crashed. Und weil ich die nicht awaite, wäre die Exception dann weg.

War das so weit verständlich? ;D

Danke!

0
Palladin007  21.02.2022, 23:37
@payed99170
Die objectsQueue soll abgearbeitet werden, und sobald es einmal durch ist wieder hinten angestellt werden.

Das habe ich schon befürchtet - also ein selbst gefrickeltes await. Mit einem richtigen await bräuchtest Du das alles nicht ;)
Aber zum Glück hast Du mit ContinueWith kein rekursiven Aufruf aufgebaut. Das wäre zwar einfacher gewesen, hätte mit der Zeit aber in einer StackOverflowException geendet. Da sind die Queues - auf dein Wissen bezogen - ein geschickter Umweg, wenn auch völlig überflüssig :D

Und sobald einer davon "fertig" ist, soll er das erste Element in der Queue nehmen, was damit machen und wieder hinten anstellen. Deswegen 2 Queues.

Das verstehe ich nicht. Aber ist euch egal, zumindest im gezeigten Code würde eine Queue mit beiden Objekten reichen und Du hättest das potentielle Fehlerrisiko nicht.

Ich blocke den UI Thread nicht, weil der Observe Task ebenfalls mit .ContinueWith aufgerufen wird

Stimmt, das habe ich nicht bedacht :D
Aber dennoch läuft das alles im UI-Thread, solange das für Selenium nicht notwendig ist, ist das unnötig.

Die sollen ja parallel laufen und sich, sobald fertig, ein neues Element aus der objectsQueue ziehen.

Für sowas gibt's Task.WhenAll - wenn alle Tasks ungefähr ähnlich lange laufen und/oder es nicht schlimm ist, wenn dazwischen mal etwas Zeit vergeht.
Ansonsten gibt's seit .NET 6 Parallel.ForEachAsync, selber damit gearbeitet habe ich aber noch nicht.

Oder Du lässt eine Methode nur einen Browser überwachen und rufst die Methode dann mehrfach auf, ohne await. Die Tasks landen dann in einer Liste, damit Du auf die Beendigung warten kannst, wenn Du abbrechen willst.

Oder Du rufst alles einfach hintereinander auf, wenn jede Operation nicht sehr lange dauert, ist das vermutlich die beste Option.

Mit Selenium kann man Browser steuern.

MUSST Du den Browser steuern?
Wenn dir das JavaScript egal ist, könntest Du auch einfach direkt die Web-API dahinter nutzen, ggf. ist das bedeutend einfacher. Sowas wie Authentifizierung könnte das aber schwerer, wenn auch nicht unmöglich machen.
Wenn es nur HTTP-Requests sind, dann sind es mit Glück (wenn es eine REST-API ist) nur einfache Methoden-Aufrufe (das Framework Refit mach das fast vollständig automatisch).

1
Palladin007  21.02.2022, 23:43
@Palladin007

PS:

Mir fällt gerade auf, dass das "Exception-Handling" ggf. auch Exceptions ignoriert, wenn es mehrere zu kurz hintereinander gibt.

1
payed99170 
Beitragsersteller
 22.02.2022, 00:08
@Palladin007

Task.WhenAll ist keine Option, da die Tasks ja nicht zum gleichen Zeitpunkt anfangen / enden.

Parallel.ForEachAsync hilft mir da auch nicht :(

Eine Queue reicht aus dem selben Grund auch nicht. Angenommen es gibt 5 Objekte und 2 Observer

Observer 1 nimmt Objekt 1

Observer 2 nimmt Objekt 2

//objectsQueue = 3, 4, 5

Einer wird fertig, wir nehmen an es ist Observer 1:

//objectsQueue = 3, 4, 5, 1 - observerQueue = Observer 1

--> Observer 1 nimmer Objekt 3

usw.

Es soll wirklich so sein, dass sobald ein Observer fertig ist, er direkt das nächste Element aus der Queue bearbeitet.

Oder Du rufst alles einfach hintereinander auf, wenn jede Operation nicht sehr lange dauert, ist das vermutlich die beste Option.

Das kann ich nicht machen, dann würde mir auch ein Observer bzw. Browser reichen, der alles macht.

Oder Du lässt eine Methode nur einen Browser überwachen und rufst die Methode dann mehrfach auf, ohne await. Die Tasks landen dann in einer Liste, damit Du auf die Beendigung warten kannst, wenn Du abbrechen willst.

Ich glaube, so etwas ähnliches hab ich schon. Es gibt einen anderen Observer, der immer die gleichen Objekte observed:

while(true){
  for(int i = 0; i < objectsToObserve.Count; i++)
  {
    //observe
    if(i == objectsToObserve.Count - 1)
      i = -1 //von vorne gehts los
  } 
}

hast du so etwas gemeint?

Das funktioniert auch, aber ich wollte es so probieren, dass eben mehrere Observer EINE Liste gemeinsam bearbeiten können

MUSST Du den Browser steuern?

Ja, die API ist nicht öffentlich und es sind glaub WebSockets ^^

Aber dennoch läuft das alles im UI-Thread, solange das für Selenium nicht notwendig ist, ist das unnötig.

Und das würde ich wie ändern? :D

Mir fällt gerade auf, dass das "Exception-Handling" ggf. auch Exceptions ignoriert, wenn es mehrere zu kurz hintereinander gibt.

Sollte egal sein. Sobald eine Exception durch kommt, bedeutet das eigentlich, dass Chrome abgestürzt ist oder irgendwas anderes gar nicht mehr geht --> neustarten ;)

0
Palladin007  22.02.2022, 00:40
@payed99170
Ja, die API ist nicht öffentlich und es sind glaub WebSockets ^^

Es sind immer WebSockets, aber ich bezweifle, dass die Website direkt damit arbeitet.
Jede Website hat eine Web-API, nur ist sie nicht immer dokumentiert oder für den direkten Aufruf geeignet.
Du kannst z.B. auch eine Form-Action aus Programmen wie Postman aufrufen, es ist umständlich, aber möglich.

Ob sich die Arbeit lohnt, weiß ich nicht.

Und das würde ich wie ändern? :D

Ein unabhängiger Task und/oder richtiges async + ConfigureAwait(false).
Aber wie gesagt: async/await ist kein einfaches Thema und würde den Rahmen hier um ein vielfaches sprengen - außerdem hab ich keine Lust, es zu erklären.

Sollte egal sein. Sobald eine Exception durch kommt, bedeutet das eigentlich, dass Chrome abgestürzt ist oder irgendwas anderes gar nicht mehr geht --> neustarten ;)

Dann solltest Du genau diesen Fall prüfen und nur dann ignorieren und neu starten. Alle anderen Fälle sollten durch gelassen oder wenigstens geloggt werden, damit alle vermeidbaren Fehler gefunden werden.

hast du so etwas gemeint?

Ich meine sowas:

var tasks = new List<Task>();

tasks.Add(ObserveAsync(observer1, objectsList1, observationCancellationTokenSource.Token));
tasks.Add(ObserveAsync(observer2, objectsList2, observationCancellationTokenSource.Token));

async Task ObserveAsync(Observer observer, IReadOnlyList<MyObservableObject> objectsList, CancellationToken cancellationToken)
{
    for (var i = 0; i <= objectsList.Count; i++)
    {
        try
        {
            await observer.Observe(objectsList[i], cancellationToken).ConfigureAwait(false);
        }
        catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
        {
            break;
        }
        catch (MyCoolException ex) when (ex.AnyCondition)
        {
            // Ignore
        }
        catch (Exception ex)
        {
            _logger.LogObserveFailed(ex);
        }
    }
}

Wenn jeder Browser nur ein Objekt verarbeiten kann, dann kann der die objects-Liste auch als simple Schleife abarbeiten.
Aber Vorsicht mit "ConfigureAwait", das kann Vorteile haben, aber auch Probleme verursachen. Du kannst aber nur wirklich verstehen, was das tut, wenn Du auch verstanden hast, wie Tasks arbeiten.

Und Abbrechen:

observationCancellationTokenSource.Cancel();

await Task.WhenAll(tasks);
0

Lass dir Hintegrundarbreit in seperatern Thread erledigen und invoke die Datein in dein GUI dann stürzt deim Programm nicht ab wenn Verbindungsprobleme auftreten, das passiert öfter, wenn Datenbank updates gemacht werden und das Programm plötzlich einfriert.

Woher ich das weiß:Berufserfahrung

payed99170 
Beitragsersteller
 21.02.2022, 22:06

Passiert schon :D
Die Funktion hier wird von nem ObserverSerivce auch ohne await aufgerufen.     

            observerTask = ...Observe(..., _cts.Token);
            _ = observerTask.ContinueWith(x =>
            {
                //Restart
            }, TaskContinuationOptions.OnlyOnFaulted);

Aber ich brauch halt trotzdem die Exception von dem einzelnen Observer

0