68 votes

Exceptions non gérées dans BackgroundWorker

J'ai une petite application WinForms qui utilise un objet BackgroundWorker pour effectuer une opération de longue durée.

L'opération en arrière-plan génère des exceptions occasionnelles, généralement lorsque quelqu'un a un fichier ouvert qui est en train d'être recréé.

Que le code soit exécuté depuis l'IDE ou non, .NET affiche une boîte de dialogue d'erreur informant l'utilisateur qu'une exception non gérée s'est produite. La compilation du code à l'aide de la configuration Release n'y change rien non plus.

Selon MSDN :

Si l'opération lève une exception que votre code ne gère pas, le BackgroundWorker capture l'exception et la transmet au gestionnaire d'événements RunWorkerCompleted, où elle est exposée comme la propriété Error de System.ComponentModel..: :.RunWorkerCompletedEventArgs. Si vous vous exécutez sous le débogueur Visual Studio, le débogueur s'interrompra à l'endroit du gestionnaire d'événements DoWork où l'exception non gérée a été levée.

Je m'attends à ce que ces exceptions soient levées à l'occasion et j'aimerais les traiter dans l'événement RunWorkerCompleted plutôt que dans DoWork. Mon code fonctionne correctement et l'erreur est traitée correctement dans l'événement RunWorkerCompleted, mais je n'arrive pas à trouver comment empêcher la boîte de dialogue d'erreur .NET se plaignant de l'exception non gérée de se produire.

Le BackgroundWorker n'est-il pas censé corriger cette erreur automatiquement ? N'est-ce pas ce que dit la documentation MSDN ? Que dois-je faire pour informer .NET que cette erreur est tout en permettant à l'exception de se propager dans la propriété Error des RunWorkerCompletedEventArgs ?

125voto

Judah Himango Points 27365

Ce que vous décrivez n'est pas le comportement défini de BackgroundWorker. Vous faites quelque chose de mal, je le soupçonne.

Voici un petit exemple qui prouve que BackgroundWorker mange les exceptions en DoWork et les met à votre disposition dans RunWorkerCompleted :

var worker = new BackgroundWorker();
worker.DoWork += (sender, e) => 
    { 
        throw new InvalidOperationException("oh shiznit!"); 
    };
worker.RunWorkerCompleted += (sender, e) =>
    {
        if(e.Error != null)
        {
            MessageBox.Show("There was an error! " + e.Error.ToString());
        }
    };
worker.RunWorkerAsync();

Mes compétences psychiques de débogage me révèlent votre problème : Vous accédez à e.Result dans votre gestionnaire RunWorkerCompleted -- s'il y a une e.Error, vous devez la traiter sans accéder à e.Result. Par exemple, le code suivant est mauvais, mauvais, mauvais, et il lèvera une exception au moment de l'exécution :

var worker = new BackgroundWorker();
worker.DoWork += (sender, e) => 
    { 
        throw new InvalidOperationException("oh shiznit!"); 
    };
worker.RunWorkerCompleted += (sender, e) =>
    {
        // OH NOOOOOOOES! Runtime exception, you can't access e.Result if there's an
        // error. You can check for errors using e.Error.
        var result = e.Result; 
    };
worker.RunWorkerAsync();

Voici une implémentation correcte du gestionnaire d'événement RunWorkerCompleted :

private void RunWorkerCompletedHandler(object sender, RunWorkerCompletedEventArgs e)
{
    if (e.Error == null)
    {
       DoSomethingWith(e.Result); // Access e.Result only if no error occurred.
    }
}

VOILA, vous ne recevrez pas d'exceptions d'exécution.

37voto

Mark Cranness Points 414

J'ajouterais à la Texte MSDN :

Si l'opération soulève une exception que votre code ne gère pas, le BackgroundWorker capture l'exception et la transmet au gestionnaire d'événement RunWorkerCompleted, où elle est exposée comme la propriété Error de System.ComponentModel..: :.RunWorkerCompletedEventArgs. Si vous vous exécutez sous le débogueur Visual Studio, le débogueur s'interrompra à l'endroit du gestionnaire d'événements DoWork où l'exception non gérée a été levée.

... ET le débogueur rapportera l'exception comme "~Exception was unhandled by user code".

Solution : Ne pas exécuter sous le débogueur et cela fonctionne comme prévu : Exception attrapée dans e.Error.

2voto

Rich Points 361

C'est une vieille question, mais je l'ai trouvée en cherchant les mêmes symptômes sur Google. Je la publie au cas où quelqu'un d'autre la trouverait pour la même raison.

La réponse de Judah est correcte, mais ce n'est pas la seule raison pour laquelle la boîte de dialogue "exception non gérée dans le code utilisateur" peut apparaître. Si une exception est levée à l'intérieur d'un constructeur sur le fil d'arrière-plan, cette exception provoquera immédiatement le dialogue et ne sera pas transmise à l'événement RunWorkerCompleted. Si vous déplacez le code incriminé en dehors de tout constructeur (vers toute autre méthode), il fonctionne comme prévu.

1voto

Bobby Cannon Points 1711

[Edit]

Judah a un bon point. Mon exemple indiquait les spécificités de la gestion de l'erreur, mais mon code causerait en fait une autre exception si une exception n'était jamais rencontrée dans la méthode DoWork. Cet exemple est correct car il montre spécifiquement les capacités de gestion des erreurs du BackgroundWorker. Cependant, si vous ne vérifiez pas si le paramètre d'erreur est nul, cela pourrait être votre problème.

[/Edit]

Je ne vois pas les mêmes résultats. Pouvez-vous afficher un petit code ? Voici mon code.

private void Form1_Load(object sender, EventArgs e)
{
    BackgroundWorker worker = new BackgroundWorker();
    worker.DoWork += new DoWorkEventHandler(worker_DoWork);
    worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
    worker.RunWorkerAsync();
}

void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    // Will cause another exception if an exception didn't occur.
    // We should be checking to see if e.Error is not "null".
    textBox1.Text = "Error? " + e.Error;
}

void worker_DoWork(object sender, DoWorkEventArgs e)
{
    for (int i = 0; i < 10; i++)
    {
        if (i < 5)
        {
            Thread.Sleep(100);
        }
        else
        {
            throw new Exception("BOOM");
        }   
    }
}

Sortie du programme :

Erreur ? System.Exception : BOOM à BackgroundException.Form1.worker_DoWork(Object sender, DoWorkEventArgs e) dans D:\Workspaces\Sandbox\BackgroundException\BackgroundException\Form1.cs :ligne 43 at System.ComponentModel.BackgroundWorker.OnDoWork(DoWorkEventArgs e) at System.ComponentModel.BackgroundWorker.WorkerThreadStart(Object argument)

Un article intéressant qui ressemble à votre question. Il comporte une section sur la gestion des exceptions.

http://www.developerdotstar.com/community/node/671

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X