C# Ausnahmen in Async

Ghost_Rider_R

Lieutenant
Registriert
Nov. 2009
Beiträge
752
Hallo zusammen,

kann mir jemand sagen, warum die Ausnahme hier nicht gefangen wird und wie man es besser macht?

C#:
class Program
{
    static void Main()
    {
        Console.WriteLine("Starten...");
        try
        {
            Task ausnahmeTask = WerfenAusnahme();
            ausnahmeTask.Wait();
            Console.WriteLine("Hat alles geklappt...");
        }
        catch (Exception fehler)
        {
            Console.WriteLine(fehler.Message);
        }
        Console.WriteLine("Ende");
        Console.ReadLine();
    }

    static Task WerfenAusnahme()
    {
        return Task.Run(() => throw new Exception("Ich bin ein Fehler."));
    }
}

Ich möchte die Ausnahme gerne im Main-Thread weiterverarbeiten.

Vielen Dank dafür

LG Ghost
 
Vermutlich ist deine Exception in der InnerException (= Cause).

Code:
class Program
{
    static void Main()
    {
        Console.WriteLine("Starten...");
        try
        {
            Task ausnahmeTask = WerfenAusnahme();
            ausnahmeTask.Wait();
            Console.WriteLine("Hat alles geklappt...");
        }
        catch (AggregateException e)
        {
            Console.WriteLine(e.InnerException);
        }
        Console.WriteLine("Ende");
    }

    static Task WerfenAusnahme()
    {
        return Task.Run(() => throw new Exception("Ich bin ein Fehler."));
    }
}

Und wie man es besser macht: Der Code sieht sehr fiktiv aus.
Meiner Meinung nach fliegt eine AggregateException und deine geworfene Exception ist in der InnerException. Um zu wissen, wie man es besser macht, wäre es wichtig zu wissen, was genau gemacht werden soll...
 
Zuletzt bearbeitet:
Das Problem ist, dass er die Exception gar nicht erst fängt, sondern einfach stehen bleibt, wie wenn es keinen Try-Catch Block gäbe. Bei asynchronen Methoden verhält sich das Exception-Handling anders als bei synchronen Methoden, das war mir bislang so auch nicht bekannt.
 
Bei mir nicht.

1668464299869.png


Verwende da .net 6.0
 
Meinst Du vielleicht, dass Visual Studio anhält? Wenn ich das Program mit Strg+F5 starte, bleibt er nicht stehen, nur beim Debuggen mit F5. Läuft dann beim erneuten Drücken von F5 weiter.

Wenn Dir das Verhalten nicht gefällt, kannst Du in dem Popup den Haken entfernen:
exc.png


Danach hält er auch beim normalen Debugdurchlauf mit F5 nicht an. Du kannst die Einstellungen in den Exception Settings wieder rückgängig machen. Strg+Alt+E und das kleine Symbol drücken:
exc2.png


Noch ein Hinweis:
Wie @tollertyp richtig gezeigt hat, wirft Task.Wait() nur eine AggregateException, wo Deine Exception verpackt wird. Dadurch ändert sich auch die Ausgabe auf der Konsole, wenn Du fehler.Message ausgibst. Wenn Du direkt deine eigene Exception bekommen willst muss der Code so aussehen:

C#:
ausnahmeTask.GetAwaiter().GetResult();
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: Ghost_Rider_R
Tatsächlich, das ist es @michi.o
Heißt das, ich habe es dennoch richtig abgefangen und es ist nur ein ,,Warnhinweis"?
 
Ja, ist richtig abgefangen, bzw. ich habe gerade noch einen Hinweis bei meinem letzten Post hinzugefügt.
 
  • Gefällt mir
Reaktionen: Ghost_Rider_R
Warum bleibt denn dann der Debugger da standardmäßig stehen? Das macht er doch sonst auch nicht. Beim Releasebuild funktioniert es direkt wie erwartet. Dachte jetzt die ganze Zeit, dass er die Ausnahme nicht abfängt...
 
Soweit ich das verstehe, hält er automatisch an, wenn eine Exception auftritt, die nicht in einem try/catch Block steckt. Deswegen steht in der Überschrift vom Popup "User-Unhandled".
Das Dein Aufruf von Wait() oben in einem try/catch Block steht, merkt er nicht. Da fehlt dem Debugger wahrscheinlich die Intelligenz dafür, weil Tasks im Hintergrund im Thread-Pool ausgeführt werden.

Ich habe Deinen Code gerade umgeschrieben und WerfeAusnahme() geändert.
Der Lambda Audruck ist weg und jetzt hält der Debugger nicht mehr an.

C#:
namespace ConsoleApp1
{
    class Program
    {
        static void Main()
        {
            Console.WriteLine("Starten...");
            try
            {
                Task ausnahmeTask = WerfenAusnahme();
                ausnahmeTask.GetAwaiter().GetResult();
                Console.WriteLine("Hat alles geklappt...");
            }
            catch (Exception fehler)
            {
                Console.WriteLine(fehler.Message);
            }
            Console.WriteLine("Ende");
            Console.ReadLine();
        }

        static async Task WerfenAusnahme()
        {
            await Task.Yield();
            throw new Exception("Ich bin ein Fehler.");
        }
    }
}
Das Yield() machts nichts. Das habe ich nur eingefügt, damit er nicht wegen dem fehlenden await meckert.
 
Zuletzt bearbeitet:
…jetzt fehlt aber auch das Task.Run. Alles innerhalb dieser Methode verursacht diese Annomalie.
 
Ghost_Rider_R schrieb:
Ich möchte die Ausnahme gerne im Main-Thread weiterverarbeiten.
So sagst Du, aber warum? Was spricht gegen threadlokale Verarbeitung?
Ganz dämlich formuliert, wenn du ne Anwendung hast mit 20+ Threads, und einer wirft eine Ausnahme, und du vergißt die im Main thread abzufangen, dann fliegt dir nicht der Thread, sondern die Anwendung um die Ohren.
Obendrauf hast irgendwann das Problem daß du ne ellenlange Liste von catch im Try/catch bekommst, und/oder du nicht mehr differenziert "catch"en kannst, weil fünf Threads IOExceptions werfen können und aber der jeweilige Catch eher thread-spezifisch als application-spezifisch wäre.

Kannst das natürlich so machen, klar, aber imvho is das eher schlechtes Design mit Ansage.
 
Am besten gar nicht erst angewöhnen syncronen und asyncronen Code mittels GetAwaiter , Result oder Wait zu mischen. Das führt nur in den Deadlock.
Besser:

C#:
static async Task Main()
{
    Console.WriteLine("Starten...");
    try
    {
        await WerfenAusnahme();
        Console.WriteLine("Hat alles geklappt...");
    }
    catch (Exception fehler)
    {
        Console.WriteLine(fehler.Message);
    }
    Console.WriteLine("Ende");
    Console.ReadLine();
}
 
  • Gefällt mir
Reaktionen: Dalek, marcOcram, Drexel und eine weitere Person
Ich denke Tasks sind eine tolle und relativ leicht zu überschauende Möglichkeit, um Prozesse deutlich zu beschleunigen. Häufig müssen diese Prozesse aber einen Wert zurück geben und innerhalb des Tasks kann es eben ab und an auch mal knallen. Daher wüsste ich hier gerne, wie dies am elegantesten gelöst werden könnte, damit der Main Thread weiß, dass es einen Fehler gab.

Bei meinen aktuellen Projekten fange ich die Ausnahme noch innerhalb der Task.Run Anweisung ab und gebe dann das zu erwartende Ergebnis und eine etwaige Ausnahme gekapselt in einer eigenen Klasse zurück, aber das ist mehr Schreibaufwand, da man dann ja für jede Task.Run-Anweisung eine eigene TaskErgebnisKlasse schreiben muss (Inhalt: IrgendeineKlasse ergebnis und Exception? eventuellerFehler) wenn man es auch anders lösen könnte.
 
Das sind so ziemlich die beiden Möglichkeiten:
  • Result-Object mit Kapselung für Exception
  • try/catch im Aufrufer.

Wenn auf den Task "awaited" wird, dann kannst du die Exception normal fangen.

C#:
static async Task Main()
{
    try
    {
        string message = await GetString();
    }
    catch (InvalidOperationException ex)
    {
        Console.WriteLine(ex.Message);
    }
}

private static Task<string> GetString()
{
    return Task.Run(() =>
    {
        // throw new InvalidOperationException("foobar");
        return string.Empty;
    });
}

Du kannst das Task.Run auch mit einem ExceptionHandler kapseln und zentral darauf reagieren. In diesem Fall brauchst du auch nicht "awaiten":
C#:
public class ExceptionHandler
{
    public Task RunTask(Func<Task> function)
    {
        Task task = Task.Run(function);
        return CatchExceptions(task);
    }

    public Task RunTask(Action action)
    {
        Task task = Task.Run(action);
        return CatchExceptions(task);
    }

    private Task CatchExceptions(Task task)
    {
        return task.ContinueWith(task =>
        {
            if (task.IsFaulted)
                Console.WrileLine(task.Exception.InnerException);
        }, TaskScheduler.Current);
    }
}
 
  • Gefällt mir
Reaktionen: Ghost_Rider_R
@andy_m4
Das ist schon richtig, macht meinen Code so aber wesentlich übersichtlicher und hier würde ich gerne dem KISS-Principle folgen :-).

Unabhängig davon habe ich das gefunden:
https://learn.microsoft.com/de-de/d...ming/exception-handling-task-parallel-library

1668521539136.png


Demzufolge ist das wohl durchaus eine akzeptable Lösung für mein Problem:

C#:
class Program
{
    public static void Main()
    {
        Console.WriteLine("Go");
        var task = Task.Run(() => throw new Exception("This exception is expected!"));
        try
        {
            task.Wait();
        }
        catch (AggregateException ae)
        {
            foreach (var ex in ae.InnerExceptions)
            {
                // Handle the custom exception.
                if (ex is Exception)
                {
                    Console.WriteLine(ex.Message);
                }
                // Rethrow any other exception.
                else
                {
                    throw ex;
                }
            }
        }
        Console.WriteLine("Ende");
        Console.ReadLine();
    }
}
// The example displays the following output:
//        This exception is expected!

Vielen Dank für eure Hilfe!

LG Ghost
 
Zurück
Oben