it-swarm.com.de

Fängt eine Ausnahme ab, die durch eine asynchrone Leermethode ausgelöst wurde

Kann mit dem asynchronen CTP von Microsoft für .NET eine von einer asynchronen Methode in der aufrufenden Methode ausgelöste Ausnahme abgefangen werden?

public async void Foo()
{
    var x = await DoSomethingAsync();

    /* Handle the result, but sometimes an exception might be thrown.
       For example, DoSomethingAsync gets data from the network
       and the data is invalid... a ProtocolException might be thrown. */
}

public void DoFoo()
{
    try
    {
        Foo();
    }
    catch (ProtocolException ex)
    {
          /* The exception will never be caught.
             Instead when in debug mode, VS2010 will warn and continue.
             The deployed the app will simply crash. */
    }
}

Im Grunde möchte ich, dass die Ausnahme vom asynchronen Code in meinen aufrufenden Code aufsteigt, wenn dies überhaupt möglich ist.

251
TimothyP

Es ist etwas seltsam zu lesen, aber ja, die Ausnahme sprudelt bis zum aufrufenden Code - aber nur , wenn Sie await oder Wait() den Aufruf an Foo.

public async Task Foo()
{
    var x = await DoSomethingAsync();
}

public async void DoFoo()
{
    try
    {
        await Foo();
    }
    catch (ProtocolException ex)
    {
          // The exception will be caught because you've awaited
          // the call in an async method.
    }
}

//or//

public void DoFoo()
{
    try
    {
        Foo().Wait();
    }
    catch (ProtocolException ex)
    {
          /* The exception will be caught because you've
             waited for the completion of the call. */
    }
} 

Asynchrone leere Methoden haben unterschiedliche Semantiken für die Fehlerbehandlung. Wenn eine Ausnahme aus einer asynchronen Aufgabe oder einer asynchronen Aufgabenmethode geworfen wird, wird diese Ausnahme erfasst und auf dem Aufgabenobjekt platziert. Bei asynchronen void-Methoden gibt es kein Task-Objekt. Daher werden alle Ausnahmen, die aus einer asynchronen void-Methode ausgelöst wurden, direkt im SynchronizationContext ausgelöst, der beim Starten der asynchronen void-Methode aktiv war. - https://msdn.Microsoft.com/en-us/magazine/jj991977.aspx

Beachten Sie, dass die Verwendung von Wait () dazu führen kann, dass Ihre Anwendung blockiert wird, wenn .Net beschließt, Ihre Methode synchron auszuführen.

Diese Erklärung http://www.interact-sw.co.uk/iangblog/2010/11/01/csharp5-async-exceptions ist ziemlich gut - sie beschreibt die Schritte, die der Compiler unternimmt, um dies zu erreichen Magie.

233
Stuart

Der Grund, warum die Ausnahme nicht abgefangen wird, liegt darin, dass die Foo () -Methode einen void-Rückgabetyp hat und beim Aufruf von await einfach zurückgibt. Da DoFoo () nicht auf den Abschluss von Foo wartet, kann der Ausnahmehandler nicht verwendet werden.

Dies eröffnet eine einfachere Lösung, wenn Sie die Methodensignaturen ändern können - ändern Sie Foo(), sodass der Typ Task und dann DoFoo() can await Foo() zurückgegeben wird. , wie in diesem Code:

public async Task Foo() {
    var x = await DoSomethingThatThrows();
}

public async void DoFoo() {
    try {
        await Foo();
    } catch (ProtocolException ex) {
        // This will catch exceptions from DoSomethingThatThrows
    }
}
70
Rob Church

Ihr Code entspricht nicht Ihren Vorstellungen. Asynchrone Methoden werden sofort zurückgegeben, nachdem die Methode auf das asynchrone Ergebnis gewartet hat. Es ist aufschlussreich, die Ablaufverfolgung zu verwenden, um zu untersuchen, wie sich der Code tatsächlich verhält.

Der folgende Code bewirkt Folgendes:

  • Erstelle 4 Aufgaben
  • Jede Aufgabe erhöht asynchron eine Zahl und gibt die erhöhte Zahl zurück
  • Wenn das asynchrone Ergebnis eingetroffen ist, wird es verfolgt.

static TypeHashes _type = new TypeHashes(typeof(Program));        
private void Run()
{
    TracerConfig.Reset("debugoutput");

    using (Tracer t = new Tracer(_type, "Run"))
    {
        for (int i = 0; i < 4; i++)
        {
            DoSomeThingAsync(i);
        }
    }
    Application.Run();  // Start window message pump to prevent termination
}


private async void DoSomeThingAsync(int i)
{
    using (Tracer t = new Tracer(_type, "DoSomeThingAsync"))
    {
        t.Info("Hi in DoSomething {0}",i);
        try
        {
            int result = await Calculate(i);
            t.Info("Got async result: {0}", result);
        }
        catch (ArgumentException ex)
        {
            t.Error("Got argument exception: {0}", ex);
        }
    }
}

Task<int> Calculate(int i)
{
    var t = new Task<int>(() =>
    {
        using (Tracer t2 = new Tracer(_type, "Calculate"))
        {
            if( i % 2 == 0 )
                throw new ArgumentException(String.Format("Even argument {0}", i));
            return i++;
        }
    });
    t.Start();
    return t;
}

Wenn Sie die Spuren beobachten

22:25:12.649  02172/02820 {          AsyncTest.Program.Run 
22:25:12.656  02172/02820 {          AsyncTest.Program.DoSomeThingAsync     
22:25:12.657  02172/02820 Information AsyncTest.Program.DoSomeThingAsync Hi in DoSomething 0    
22:25:12.658  02172/05220 {          AsyncTest.Program.Calculate    
22:25:12.659  02172/02820 {          AsyncTest.Program.DoSomeThingAsync     
22:25:12.659  02172/02820 Information AsyncTest.Program.DoSomeThingAsync Hi in DoSomething 1    
22:25:12.660  02172/02756 {          AsyncTest.Program.Calculate    
22:25:12.662  02172/02820 {          AsyncTest.Program.DoSomeThingAsync     
22:25:12.662  02172/02820 Information AsyncTest.Program.DoSomeThingAsync Hi in DoSomething 2    
22:25:12.662  02172/02820 {          AsyncTest.Program.DoSomeThingAsync     
22:25:12.662  02172/02820 Information AsyncTest.Program.DoSomeThingAsync Hi in DoSomething 3    
22:25:12.664  02172/02756          } AsyncTest.Program.Calculate Duration 4ms   
22:25:12.666  02172/02820          } AsyncTest.Program.Run Duration 17ms  ---- Run has completed. The async methods are now scheduled on different threads. 
22:25:12.667  02172/02756 Information AsyncTest.Program.DoSomeThingAsync Got async result: 1    
22:25:12.667  02172/02756          } AsyncTest.Program.DoSomeThingAsync Duration 8ms    
22:25:12.667  02172/02756 {          AsyncTest.Program.Calculate    
22:25:12.665  02172/05220 Exception   AsyncTest.Program.Calculate Exception thrown: System.ArgumentException: Even argument 0   
   at AsyncTest.Program.c__DisplayClassf.Calculateb__e() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 124   
   at System.Threading.Tasks.Task`1.InvokeFuture(Object futureAsObj)    
   at System.Threading.Tasks.Task.InnerInvoke()     
   at System.Threading.Tasks.Task.Execute()     
22:25:12.668  02172/02756 Exception   AsyncTest.Program.Calculate Exception thrown: System.ArgumentException: Even argument 2   
   at AsyncTest.Program.c__DisplayClassf.Calculateb__e() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 124   
   at System.Threading.Tasks.Task`1.InvokeFuture(Object futureAsObj)    
   at System.Threading.Tasks.Task.InnerInvoke()     
   at System.Threading.Tasks.Task.Execute()     
22:25:12.724  02172/05220          } AsyncTest.Program.Calculate Duration 66ms      
22:25:12.724  02172/02756          } AsyncTest.Program.Calculate Duration 57ms      
22:25:12.725  02172/05220 Error       AsyncTest.Program.DoSomeThingAsync Got argument exception: System.ArgumentException: Even argument 0  

Server stack trace:     
   at AsyncTest.Program.c__DisplayClassf.Calculateb__e() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 124   
   at System.Threading.Tasks.Task`1.InvokeFuture(Object futureAsObj)    
   at System.Threading.Tasks.Task.InnerInvoke()     
   at System.Threading.Tasks.Task.Execute()     

Exception rethrown at [0]:      
   at System.Runtime.CompilerServices.TaskAwaiter.EndAwait()    
   at System.Runtime.CompilerServices.TaskAwaiter`1.EndAwait()  
   at AsyncTest.Program.DoSomeThingAsyncd__8.MoveNext() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 106    
22:25:12.725  02172/02756 Error       AsyncTest.Program.DoSomeThingAsync Got argument exception: System.ArgumentException: Even argument 2  

Server stack trace:     
   at AsyncTest.Program.c__DisplayClassf.Calculateb__e() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 124   
   at System.Threading.Tasks.Task`1.InvokeFuture(Object futureAsObj)    
   at System.Threading.Tasks.Task.InnerInvoke()     
   at System.Threading.Tasks.Task.Execute()     

Exception rethrown at [0]:      
   at System.Runtime.CompilerServices.TaskAwaiter.EndAwait()    
   at System.Runtime.CompilerServices.TaskAwaiter`1.EndAwait()  
   at AsyncTest.Program.DoSomeThingAsyncd__8.MoveNext() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 0      
22:25:12.726  02172/05220          } AsyncTest.Program.DoSomeThingAsync Duration 70ms   
22:25:12.726  02172/02756          } AsyncTest.Program.DoSomeThingAsync Duration 64ms   
22:25:12.726  02172/05220 {          AsyncTest.Program.Calculate    
22:25:12.726  02172/05220          } AsyncTest.Program.Calculate Duration 0ms   
22:25:12.726  02172/05220 Information AsyncTest.Program.DoSomeThingAsync Got async result: 3    
22:25:12.726  02172/05220          } AsyncTest.Program.DoSomeThingAsync Duration 64ms   

Sie werden feststellen, dass die Run-Methode für Thread 2820 abgeschlossen ist, während nur ein untergeordneter Thread beendet wurde (2756). Wenn Sie Ihre Methode await mit try/catch umgehen, können Sie die Ausnahme auf die übliche Weise "abfangen", obwohl Ihr Code auf einem anderen Thread ausgeführt wird, wenn die Berechnungsaufgabe abgeschlossen und Ihre Weiterleitung ausgeführt wurde.

Die Berechnungsmethode verfolgt die ausgelöste Ausnahme automatisch, da ich die Datei ApiChange.Api.dll aus dem Tool ApiChange verwendet habe. Tracing und Reflector helfen viel, um zu verstehen, was los ist. Um das Threading loszuwerden, können Sie Ihre eigenen Versionen von GetAwaiter BeginAwait und EndAwait erstellen und keine Aufgabe, sondern z. a Lazy and trace in Ihren eigenen Erweiterungsmethoden. Dann werden Sie viel besser verstehen, was der Compiler und was die TPL macht.

Jetzt sehen Sie, dass es keine Möglichkeit gibt, Ihre Ausnahmebedingung zurückzugewinnen, da kein Stack-Frame mehr vorhanden ist, von dem sich Ausnahmebedingungen ausbreiten können. Ihr Code führt möglicherweise etwas völlig anderes aus, nachdem Sie die asynchronen Vorgänge eingeleitet haben. Es könnte Thread.Sleep aufrufen oder sogar beenden. Solange noch ein Vordergrund-Thread vorhanden ist, führt Ihre Anwendung weiterhin problemlos asynchrone Aufgaben aus.


Sie können die Ausnahme innerhalb der asynchronen Methode behandeln, nachdem Ihre asynchrone Operation beendet wurde, und den UI-Thread erneut aufrufen. Die empfohlene Methode hierfür ist TaskScheduler.FromSynchronizationContext . Das funktioniert nur, wenn Sie einen UI-Thread haben und mit anderen Dingen nicht viel zu tun haben.

17
Alois Kraus

Die Ausnahme kann in der asynchronen Funktion abgefangen werden.

public async void Foo()
{
    try
    {
        var x = await DoSomethingAsync();
        /* Handle the result, but sometimes an exception might be thrown
           For example, DoSomethingAsync get's data from the network
           and the data is invalid... a ProtocolException might be thrown */
    }
    catch (ProtocolException ex)
    {
          /* The exception will be caught here */
    }
}

public void DoFoo()
{
    Foo();
}

Es ist auch wichtig zu beachten, dass Sie die chronologische Stapelspur der Ausnahme verlieren, wenn Sie einen ungültigen Rückgabetyp für eine asynchrone Methode haben. Ich würde empfehlen, Task wie folgt zurückzugeben. Das Debuggen wird viel einfacher.

public async Task DoFoo()
    {
        try
        {
            return await Foo();
        }
        catch (ProtocolException ex)
        {
            /* Exception with chronological stack trace */     
        }
    }
4
rohanjansen

In diesem Blog wird Ihr Problem genau erklärt Async Best Practices .

Der Kern davon ist, dass Sie void nicht als Rückgabe für eine asynchrone Methode verwenden sollten, es sei denn, es ist eine asynchrone Ereignisbehandlungsroutine ;-).

Es wird empfohlen, den Rückgabetyp in Task zu ändern. Versuchen Sie außerdem, den gesamten Weg durch die asynchrone Codierung zu gehen, jeden asynchronen Methodenaufruf auszuführen und von asynchronen Methoden aufgerufen zu werden. Mit Ausnahme einer Main-Methode in einer Konsole, die nicht asynchron sein kann (vor C # 7.1).

Bei GUI- und ASP.NET-Anwendungen treten Deadlocks auf, wenn Sie diese bewährte Methode ignorieren. Der Deadlock tritt auf, weil diese Anwendungen in einem Kontext ausgeführt werden, der nur einen Thread zulässt und ihn nicht an den asynchronen Thread weitergibt. Dies bedeutet, dass die GUI synchron auf eine Rückgabe wartet, während die asynchrone Methode auf den Kontext wartet: Deadlock.

Dieses Verhalten tritt in einer Konsolenanwendung nicht auf, da es im Kontext mit einem Threadpool ausgeführt wird. Die asynchrone Methode kehrt zu einem anderen Thread zurück, der geplant wird. Aus diesem Grund funktioniert eine Testkonsolen-App, aber dieselben Aufrufe blockieren auch in anderen Anwendungen ...

1