Domanda

Utilizzando il CTP Async di Microsoft per .NET, è possibile catturare un'eccezione lanciata con un metodo Async nel metodo di chiamata?

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. */
    }
}

Quindi, fondamentalmente, voglio che l'eccezione del codice Async si completa nel mio codice di chiamata, se ciò è anche possibile.

È stato utile?

Soluzione

È un po 'strano da leggere, ma sì, l'eccezione bolle fino al codice chiamante - ma solo se tu await o Wait() la chiamata a 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. */
    }
} 

I metodi vuoti asincroni hanno una semantica di gestione degli errori diversi. Quando un'eccezione viene lanciata da un'attività asincrona o metodo dell'attività asincrona, tale eccezione viene acquisita e posizionata sull'oggetto attività. Con i metodi del vuoto asincrone, non esiste un oggetto di attività, quindi eventuali eccezioni gettate fuori da un metodo asincrero verranno sollevate direttamente sul Context di sincronizzazione che era attivo quando è iniziato il metodo del vuoto asincrero. - https://msdn.microsoft.com/en-us/magazine/jj991977.aspx

Si noti che l'utilizzo di Wait () può causare il blocco dell'applicazione, se .NET decide di eseguire il metodo in modo sincrono.

Questa spiegazione http://www.interact-sw.co.uk/iangblog/2010/11/01/csharp5-async-exceptions è abbastanza buono: discute i passaggi che il compilatore intraprende per raggiungere questa magia.

Altri suggerimenti

Il motivo per cui l'eccezione non è catturata è perché il metodo Foo () ha un tipo di ritorno vuoto e quindi quando viene chiamato, restituisce semplicemente. Poiché Dofoo () non attende il completamento di FOO, il gestore delle eccezioni non può essere utilizzato.

Questo apre una soluzione più semplice se è possibile modificare le firme del metodo - altera Foo() in modo che restituisca il tipo Task poi DoFoo() Potere await Foo(), come in questo codice:

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

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

Il tuo codice non fa quello che potresti pensare che faccia. I metodi asincroni tornano immediatamente dopo che il metodo inizia ad aspettare il risultato asincrone. È perspicace usare la traccia per indagare su come si sta effettivamente comportando.

Il codice seguente fa quanto segue:

  • Crea 4 compiti
  • Ogni attività aumenterà in modo asincrono un numero e restituirà il numero incrementato
  • Quando è arrivato il risultato asincrone, è rintracciato.

 

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;
}

Quando osservi le tracce

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   

Noterai che il metodo di corsa si completa sul thread 2820 mentre è terminato un solo thread figlio (2756). Se metti una prova/cattura intorno al metodo di attesa, puoi "catturare" l'eccezione nel solito modo sebbene il tuo codice sia eseguito su un altro thread quando l'attività di calcolo è terminata e la contiura viene eseguita.

Il metodo di calcolo traccia automaticamente l'eccezione lanciata perché ho usato apichange.api.dll dal Apichange attrezzo. La traccia e il riflettore aiutano molto a capire cosa sta succedendo. Per sbarazzarti del threading puoi creare le tue versioni di getwaiter Startawait e E di inaugurazione e avvolgere non un compito ma ad esempio una pigra e traccia all'interno dei tuoi metodi di estensione. Quindi avrai molto meglio capire cosa fa il compilatore e cosa fa il TPL.

Ora vedi che non c'è modo di provare/recuperare la tua eccezione poiché non è rimasto un telaio di stack per non eccezione da cui propagarsi. Il tuo codice potrebbe fare qualcosa di totalmente diverso dopo aver avviato le operazioni asincroni. Potrebbe chiamare thread.sleep o addirittura terminare. Finché c'è un thread di primo piano lasciato, la tua applicazione continuerà felicemente a eseguire compiti asincroni.


È possibile gestire l'eccezione all'interno del metodo Async dopo che l'operazione asincrona è finita e richiamata nel thread dell'interfaccia utente. Il modo consigliato per farlo è con Taskscheduler.fromsynchronizationContext. Funziona solo se hai un thread dell'interfaccia utente e non è molto impegnato con altre cose.

L'eccezione può essere catturata nella funzione Async.

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();
}

È anche importante notare che perderai la traccia cronologica dello stack dell'eccezione se si dispone di un tipo di ritorno a vuoto su un metodo asincrone. Consiglierei di restituire l'attività come segue. Andare a rendere il debug molto più semplice.

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

Questo blog spiega il tuo problema in modo ordinato Le migliori pratiche asincroni.

L'essenza di essa non dovresti usare il vuoto come ritorno per un metodo asincrone, a meno che non sia un gestore di eventi asincroni, questa è una cattiva pratica perché non consente di catturare le eccezioni ;-).

Le migliori pratiche sarebbero quella di modificare il tipo di ritorno in attività. Inoltre, prova a codificare Async fino in fondo, fai chiamare ogni metodo Async e essere chiamato dai metodi Async. Ad eccezione di un metodo principale in una console, che non può essere asincrone (prima di C# 7.1).

Ti imbatterai in deadlock con applicazioni GUI e ASP.NET se ignori questa migliore pratica. Il deadlock si verifica perché queste applicazioni vengono eseguite su un contesto che consente solo un thread e non lo rinuncia al thread asincrone. Ciò significa che la GUI attende in modo sincrono per un ritorno, mentre il metodo Async attende il contesto: Deadlock.

Questo comportamento non avverrà in un'applicazione della console, perché funziona nel contesto con un pool di thread. Il metodo Async tornerà su un altro thread che sarà programmato. Questo è il motivo per cui un'app di console di test funzionerà, ma le stesse chiamate saranno deadlock in altre applicazioni ...

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top