web-dev-qa-db-fra.com

Comment gérer une exception Task.Run

J'ai eu un problème pour intercepter mon exception dans Task.Run. J'ai modifié mon code et mon problème a été résolu. Je suis disposé à comprendre quelle est la différence entre la gestion des exceptions dans Task.Run de ces deux manières:

Dans la fonction Outside, je ne peux pas attraper l'exception, mais à l'intérieur, je peux l'attraper.

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
Mohammad Chamanpara

Lorsqu'une tâche est exécutée, toutes les exceptions qu'elle génère sont conservées et rediffusées lorsque quelque chose attend le résultat de la tâche ou sa fin.

Task.Run() renvoie un objet Task que vous pouvez utiliser pour le faire, ainsi:

var task = Task.Run(...)

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

Pour les versions plus récentes de C #, vous pouvez utiliser await à la place de Task.Wait ():

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

ce qui est beaucoup plus net.


Pour être complet, voici une application console compilable qui illustre l'utilisation de await:

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

L'idée d'utiliser Task.Wait fera l'affaire, mais le thread appelant (comme le code l'indique) attendra et bloquera donc jusqu'à ce que la tâche soit finalisée, ce qui rend le code synchrone au lieu d'async.

Utilisez plutôt l'option Task.ContinueWith pour obtenir des résultats:

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

Si la tâche doit continuer sur le thread d'interface utilisateur, utilisez l'option TaskScheduler.FromCurrentSynchronizationContext () comme paramètre on continue avec comme suit: 

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

Ce code renverra simplement l'exception globale à partir du niveau de la tâche. Bien sûr, vous pouvez également introduire une autre forme de traitement des exceptions ici.

25
Menno Jongerius

Dans votre code extérieur, vous ne faites que vérifier si le démarrage d'une tâche ne lève pas d'exception et non le corps de la tâche elle-même. Il fonctionne de manière asynchrone et le code qui l'a initié est alors exécuté.

Vous pouvez utiliser:

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

L'utilisation de .GetAwaiter().GetResult() attend la fin de la tâche et passe l'exception renvoyée telle qu'elle est et ne les encapsule pas dans AggregateException.

3
jabko87

Vous pouvez simplement attendre, puis les exceptions se retrouvent dans le contexte de synchronisation actuel (voir la réponse de Matthew Watson). Ou, comme Menno Jongerius le mentionne, vous pouvez ContinueWith pour garder le code asynchrone. Notez que vous pouvez le faire uniquement si une exception est générée à l'aide de l'option de continuation OnlyOnFaulted:

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

Pour moi, je voulais que mon Task.Run continue après une erreur, laissant l’interface utilisateur la traiter car elle a le temps.

Ma solution (bizarre?) Est aussi d'avoir un Form.Timer en cours d'exécution. Mon Task.Run a sa file d'attente (pour les éléments de longue durée non liés à l'interface utilisateur) et mon Form.Timer a sa file d'attente (pour les éléments d'interface utilisateur). 

Etant donné que cette méthode fonctionnait déjà pour moi, il était facile d’ajouter une gestion des erreurs: si la tâche obtient une erreur, elle ajoute les informations sur cette erreur à la file d’attente Form.Timer, qui affiche le dialogue d’erreur.

0
Kurtis Lininger