wake-up-neo.com

So behandeln Sie die Task.Run-Ausnahme

Ich hatte ein Problem mit meiner Ausnahme von Task.Run. Ich habe meinen Code geändert und mein Problem gelöst. Ich bin bereit, herauszufinden, was der Unterschied zwischen der Behandlung von Ausnahmen in Task.Run auf zwei Arten ist:

In Outside-Funktion kann ich die Ausnahme nicht abfangen, aber in Inside kann ich sie abfangen.

void Outside()
{
    try
    {
        Task.Run(() =>
        {
            int z = 0;
            int x = 1 / z;
        });
    }
    catch (Exception exception)
    {
        MessageBox.Show("Outside : " + exception.Message);
    }
}

void Inside()
{
    Task.Run(() =>
    {
        try
        {
            int z = 0;
            int x = 1 / z;
        }
        catch (Exception exception)
        {
            MessageBox.Show("Inside : "+exception.Message);
        }
    });
}
22

Wenn eine Aufgabe ausgeführt wird, werden alle von ihr ausgelösten Ausnahmen beibehalten und erneut ausgelöst, wenn etwas auf das Ergebnis der Aufgabe oder den Abschluss der Aufgabe wartet.

Task.Run() gibt ein Task-Objekt zurück, das Sie dazu verwenden können.

var task = Task.Run(...)

try
{
    task.Wait(); // Rethrows any exception(s).
    ...

Für neuere Versionen von C # können Sie await anstelle von Task.Wait () verwenden:

try
{
    await Task.Run(...);
    ...

das ist viel ordentlicher.


Der Vollständigkeit halber hier eine kompilierbare Konsolenanwendung, die die Verwendung von await veranschaulicht:

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {
        static void Main()
        {
            test().Wait();
        }

        static async Task test()
        {
            try
            {
                await Task.Run(() => throwsExceptionAfterOneSecond());
            }

            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
        }

        static void throwsExceptionAfterOneSecond()
        {
            Thread.Sleep(1000); // Sleep is for illustration only. 
            throw new InvalidOperationException("Ooops");
        }
    }
}
26
Matthew Watson

Die Idee, Task.Wait zu verwenden, führt den Trick aus, bewirkt jedoch, dass der aufrufende Thread (wie der Code sagt) wartet und blockiert, bis die Task abgeschlossen ist. Dies macht den Code effektiv synchron und nicht asynchron.

Verwenden Sie stattdessen die Option Task.ContinueWith, um Ergebnisse zu erzielen:

Task.Run(() =>
{
   //do some work
}).ContinueWith((t) =>
{
   if (t.IsFaulted) throw t.Exception;
   if (t.IsCompleted) //optionally do some work);
});

Wenn die Task im UI-Thread fortgesetzt werden muss, verwenden Sie die Option TaskScheduler.FromCurrentSynchronizationContext () als Parameter on continue mit wie folgt: 

).ContinueWith((t) =>
{
    if (t.IsFaulted) throw t.Exception;
    if (t.IsCompleted) //optionally do some work);
}, TaskScheduler.FromCurrentSynchronizationContext());

Dieser Code wird einfach die Aggregatausnahme von der Taskebene erneut auslösen. Natürlich können Sie hier auch eine andere Form der Ausnahmebehandlung einführen.

25
Menno Jongerius

In Ihrem Outside-Code prüfen Sie nur, ob das Starten einer Task keine Ausnahme auslöst, nicht aber den Body der Task. Es läuft asynchron und der Code, der es initiiert hat, wird dann ausgeführt.

Sie können verwenden:

void Outside()
{
    try
    {
        Task.Run(() =>
        {
            int z = 0;
            int x = 1 / z;
        }).GetAwaiter().GetResult();
    }
    catch (Exception exception)
    {
        MessageBox.Show("Outside : " + exception.Message);
    }
}

Die Verwendung von .GetAwaiter().GetResult() wartet, bis die Task endet und die geworfene Ausnahme so weitergibt, wie sie ist und sie nicht in AggregateException einpackt.

3
jabko87

Sie können einfach warten, und dann sprudeln Ausnahmen zum aktuellen Synchronisationskontext (siehe Antwort von Matthew Watson). Oder, wie Menno Jongerius erwähnt, können Sie ContinueWith verwenden, um den Code asynchron zu halten. Beachten Sie, dass dies nur möglich ist, wenn eine Ausnahme mit der Option OnlyOnFaulted continuation ausgelöst wird:

Task.Run(()=> {
    //.... some work....
})
// We could wait now, so we any exceptions are thrown, but that 
// would make the code synchronous. Instead, we continue only if 
// the task fails.
.ContinueWith(t => {
    // This is always true since we ContinueWith OnlyOnFaulted,
    // But we add the condition anyway so resharper doesn't bark.
    if (t.Exception != null)  throw t.Exception;
}, default
     , TaskContinuationOptions.OnlyOnFaulted
     , TaskScheduler.FromCurrentSynchronizationContext());
1
Diego

Für mich wollte ich, dass Task.Run nach einem Fehler fortgesetzt wird und die Benutzeroberfläche mit dem Fehler umgehen kann, da er Zeit hat.

Meine (komische?) Lösung besteht darin, auch einen Form.Timer auszuführen. Mein Task.Run hat seine Warteschlange (für lang laufende Nicht-UI-Elemente) und mein Form.Timer hat eine Warteschlange (für UI-Elemente). 

Da diese Methode bereits für mich funktionierte, war es trivial, die Fehlerbehandlung hinzuzufügen: Wenn task.Run einen Fehler erhält, fügt es die Fehlerinformationen der Warteschlange Form.Timer hinzu, die das Fehlerdialogfeld anzeigt.

0
Kurtis Lininger