Laden...

Fehlerbehandlung mit Try/Catch innerhalb einer asynchronen Methode

Erstellt von Paschulke vor 7 Jahren Letzter Beitrag vor 7 Jahren 1.986 Views
P
Paschulke Themenstarter:in
69 Beiträge seit 2011
vor 7 Jahren
Fehlerbehandlung mit Try/Catch innerhalb einer asynchronen Methode

Hallo!

Ich habe eine Methode zum Laden eines Objekts, die asynchron aufgerufen werden soll.
Darin wird zunächst versucht das zu ladende Objekt aus einer Datei zu deserialisieren. Wenn das fehlschlägt, soll das Objekt aus einer Datenbank gelesen werden.

Hier mal ein auf die wesentlichen Aspekte rediziertes Beispiel:


public void Main()
{
    MyObject myObject = LoadMyObjectAsync().Result();

    // myObject ist null, wenn der Code durch den catch-Block läuft.
}

private async Task<MyObject> LoadMyObjectAsync()
{
    try
    {
        return await DeserialzeMyObjectAsync().ConfigureAwait(false);
    }
    catch (SerializationException)
    {
        return await db.ReadMyObjectAsync().ConfigureAwait(false);
    }
}

Stellt mal außer Frage, ob das Beispiel in der Form Sinn macht. Mir geht es darum, die Technik zu verstehen...

Ich habe festgestellt, dass innerhalb der Methode DeserialzeMyObjectAsync ein Thread erzeugt wird, der geschlossen wird, wenn der Code seinen erwarteten Weg geht. Wenn der Code in die Exception läuft, bleibt der Thread auch nach der Rückkehr in die wartende Methode bestehen. Deshalb ist myObject im aufrufenden Thread null.

Wie fange ich innerhalb der Methode "LoadMyObjectAsync" den Fehler korrekt ab? Bzw. wie fahre ich im ExceptionHandler korrekt fort?

D
985 Beiträge seit 2014
vor 7 Jahren

Probiere es doch mal mit einem Minimal-Programm aus


    class Program
    {
        static void Main( string[] args )
        {

            object deserializerObject = null; // new object();
            IDeserializer deserializer = new DummyDeserializer( deserializerObject );
            object readerObject = new object();
            IReader reader = new DummyReader( readerObject );

            var loader = new Loader( deserializer, reader );
            var result = loader.LoadMyObjectAsync().Result;

            if ( result == deserializerObject ) Console.WriteLine( "object from Deserializer" );
            if ( result == readerObject ) Console.WriteLine( "object from Reader" );
        }

        class DummyReader : IReader
        {
            private object returnObject;
            public DummyReader( object returnObject )
            {
                this.returnObject = returnObject;
            }
            public object ReadMyObject()
            {
                if ( returnObject == null ) throw new ReaderException();
                return returnObject;
            }

            public Task<object> ReadMyObjectAsync()
            {
                return Task.Run( () =>
                {
                    if ( returnObject == null ) throw new ReaderException();
                    return returnObject;
                } );
            }
        }

        class DummyDeserializer : IDeserializer
        {
            private object returnObject;

            public DummyDeserializer( object returnObject )
            {
                this.returnObject = returnObject;
            }

            public object DeserializeMyObject()
            {
                if ( returnObject == null ) throw new SerializationException();
                return returnObject;
            }

            public Task<object> DeserializeMyObjectAsync()
            {
                //return Task.FromResult( deserializerObject );
                return Task.Run( () =>
                {
                    if ( returnObject == null ) throw new SerializationException();
                    return returnObject;
                } );
            }
        }

        class SerializationException : Exception
        {
        }

        class ReaderException : Exception
        {
        }

        interface IDeserializer
        {
            object DeserializeMyObject();
            Task<object> DeserializeMyObjectAsync();
        }

        interface IReader
        {
            object ReadMyObject();
            Task<object> ReadMyObjectAsync();
        }

        class Loader
        {
            private IDeserializer deserializer;
            private IReader reader;

            public Loader( IDeserializer deserializer, IReader reader )
            {
                this.deserializer = deserializer;
                this.reader = reader;
            }

            public object LoadMyObject()
            {
                try
                {
                    return deserializer.DeserializeMyObject();
                }
                catch ( SerializationException )
                {

                    return reader.ReadMyObject();
                }
            }

            public async Task<object> LoadMyObjectAsync()
            {
                try
                {
                    return await deserializer.DeserializeMyObjectAsync();
                }
                catch ( SerializationException )
                {

                    return await reader.ReadMyObjectAsync();
                }
            }
        }
    }

Daraus könntest du jetzt auch einen Unit-Test zusammenbauen, der dir das gewünschte Verhalten auch testet.

R
74 Beiträge seit 2006
vor 7 Jahren
D
985 Beiträge seit 2014
vor 7 Jahren

Ach darum geht es ... 😁


public void Main()
{
    MyObject myObject = LoadMyObjectAsync().Result();

    // myObject ist null, wenn der Code durch den catch-Block läuft.
}

private async Task<MyObject> LoadMyObjectAsync()
{
    try
    {
        return await DeserialzeMyObjectAsync().ConfigureAwait(false);
    }
    catch (SerializationException)
    {
        // return await db.ReadMyObjectAsync().ConfigureAwait(false);
    }
    return await db.ReadMyObjectAsync().ConfigureAwait(false);
}

R
74 Beiträge seit 2006
vor 7 Jahren

glaub nicht, schau mal hier

http://stackoverflow.com/questions/16626161/a-good-solution-for-await-in-try-catch-finally


static async Task f()
{
    ExceptionDispatchInfo capturedException = null;
    try
    {
        await TaskThatFails();
    }
    catch (MyException ex)
    {
        capturedException = ExceptionDispatchInfo.Capture(ex);
    }

    if (capturedException != null)
    {
        await ExceptionHandler();

        capturedException.Throw();
    }
}

vor c#6 braucht man async/await wohl kaum verwenden

16.807 Beiträge seit 2008
vor 7 Jahren

Das Problem ist die falsche Handhabe mit Result. Darüber hinaus würde ich auch sagen, dass dieser Code keine gute Umsetzung im Allgemeinen darstellt.

Result sollte am besten gar nicht verwendet werden, weil es Deadlocks auslösen kann und Exceptions wrappt.

Dass async/await vor C# 6 nicht nutzbar ist, ist natürlich auch quatsch.

P
Paschulke Themenstarter:in
69 Beiträge seit 2011
vor 7 Jahren

Oh je... Asche auf mein Haupt!
Ich habe jetzt 2 Tage mit diesem Problem verbracht. Der schlichte, blöde Fehler war einfach nur, dass ich vergessen hatte den Rückgabewert zuzuweisen. Wie blind kann man sein...! Vor allem nachdem ich den Code noch einmal vereinfacht (das war hier mein Fehler!) hier rein gesetzt hatte...

Ich hatte also sinngemäß anstatt...


catch
{
    myObject = await db.ReadMyObjectAsync().ConfigureAwait(false);
}
return myObject;

einfach nur...


catch
{
    await db.ReadMyObjectAsync().ConfigureAwait(false);
}
return myObject;

geschrieben.

Wenn es hier eine Kaffeekasse für Dämlichkeit gäbe würde ich jetzt einen 5er rein schmeißen!

Sorry für die falsch gestellte Frage!!! Aber Eure Beiträge (vor allem der Beispielcode) haben mir trotzdem sehr geholfen. Einfach um einen anderen Blick auf das Problem zu bekommen...