BackgroundWorker vs thread d'arrière-plan

166

J'ai une question stylistique sur le choix de l'implémentation du thread d'arrière-plan que je devrais utiliser sur une application Windows Form. Actuellement, j'ai BackgroundWorkerun formulaire sur une (while(true))boucle infinie . Dans cette boucle, j'utilise WaitHandle.WaitAnypour garder le thread en sommeil jusqu'à ce que quelque chose d'intéressant se produise. Un des descripteurs d'événements sur StopThreadlesquels j'attends est un événement " " pour que je puisse sortir de la boucle. Cet événement est signalé lors de mon dépassement Form.Dispose().

J'ai lu quelque part qui BackgroundWorkerest vraiment destiné aux opérations avec lesquelles vous ne voulez pas lier l'interface utilisateur et qui ont une fin finie - comme le téléchargement d'un fichier ou le traitement d'une séquence d'éléments. Dans ce cas, la "fin" est inconnue et uniquement lorsque la fenêtre est fermée. Par conséquent, serait-il plus approprié pour moi d'utiliser un thread d'arrière-plan plutôt que BackgroundWorkerdans ce but?

freddy smith
la source

Réponses:

88

D'après ma compréhension de votre question, vous utilisez un BackgroundWorkerfil de discussion standard.

La raison pour laquelle il BackgroundWorkerest recommandé pour les choses que vous ne voulez pas lier le thread de l'interface utilisateur est parce qu'il expose de beaux événements lors du développement de Win Forms.

Les événements aiment RunWorkerCompletedsignaler quand le thread a terminé ce qu'il devait faire, et l' ProgressChangedévénement pour mettre à jour l'interface graphique sur la progression des threads.

Donc, si vous ne les utilisez pas , je ne vois aucun mal à utiliser un thread standard pour ce que vous devez faire.

ParmesanCodice
la source
un autre problème dont je ne suis pas sûr est, supposons que j'essaie de supprimer le formulaire sur lequel s'exécute le backgroundworker. Je signale le shutdownevent (ManualResetEvent) et quelque temps après, le DoWork se terminera gracieusement. Dois-je simplement laisser le formulaire aller de l'avant et Dispose même si le DoWork peut prendre un peu plus de temps à se terminer, ou y a-t-il un moyen (et est-il préférable) de thread. du formulaire continuer?
freddy smith
Je pense que BackgroundWorker.IsBusy est ce que vous recherchez là-bas.
ParmesanCodice
1
Utilisez uniquement CancelAsync(et testez CancellationPendingsi votre thread va interroger sur de courts intervalles, si vous voulez qu'une exception soit levée à la place, utilisez un System.Threading.Thread.Abort()qui lève une exception dans le bloc de thread lui-même, choisissez le bon modèle pour la situation.
Brett Ryan
369

Certaines de mes pensées ...

  1. Utilisez BackgroundWorker si vous avez une seule tâche qui s'exécute en arrière-plan et doit interagir avec l'interface utilisateur. La tâche de rassemblement des données et des appels de méthode au thread d'interface utilisateur est gérée automatiquement via son modèle basé sur les événements. Évitez BackgroundWorker si ...
    • votre assembly n'a pas ou n'interagit pas directement avec l'interface utilisateur,
    • vous avez besoin que le fil soit un fil de premier plan, ou
    • vous devez manipuler la priorité du thread.
  2. Utilisez un thread ThreadPool lorsque l'efficacité est souhaitée. Le ThreadPool permet d'éviter la surcharge associée à la création, au démarrage et à l'arrêt des threads. Évitez d'utiliser le ThreadPool si ...
    • la tâche s'exécute pendant toute la durée de vie de votre application,
    • vous avez besoin que le fil soit un fil de premier plan,
    • vous devez manipuler la priorité du thread, ou
    • vous avez besoin que le thread ait une identité fixe (abandon, suspension, découverte).
  3. Utilisez la classe Thread pour les tâches de longue durée et lorsque vous avez besoin de fonctionnalités offertes par un modèle de thread formel, par exemple, choisir entre les threads de premier plan et d'arrière-plan, ajuster la priorité du thread, contrôler finement l'exécution des threads, etc.
Matt Davis
la source
10
Le travail en arrière-plan se trouve dans l'assembly System.dll et l'espace de noms System.ComponentModel. Il n'y a aucune dépendance sur Winforms.
Kugel
17
C'est correct, mais BackgroundWorkerest conçu pour signaler la progression du thread à une partie intéressée, ce qui implique généralement une interface utilisateur. La documentation MSDN pour la classe le rend très clair. Si vous avez simplement besoin d'une tâche à effectuer en arrière-plan, préférez utiliser un ThreadPoolthread.
Matt Davis
5
En ce qui concerne votre point sur l' System.Windows.Formsassemblée; BackgroundWorkerest également utile pour les applications WPF et ces applications peuvent ne pas avoir de référence à WinForms.
GiddyUpHorsey
12

À peu près ce que Matt Davis a dit, avec les points supplémentaires suivants:

Pour moi, le principal différenciateur BackgroundWorkerest le classement automatique de l'événement terminé via le SynchronizationContext. Dans un contexte d'interface utilisateur, cela signifie que l'événement terminé se déclenche sur le thread d'interface utilisateur et peut donc être utilisé pour mettre à jour l'interface utilisateur. Il s'agit d'un différenciateur majeur si vous utilisez le BackgroundWorkerdans un contexte d'interface utilisateur.

Les tâches exécutées via le ThreadPoolne peuvent pas être facilement annulées (cela inclut ThreadPool. QueueUserWorkItemEt les délégués s'exécutent de manière asyncronale). Ainsi, même si cela évite la surcharge de la rotation des threads, si vous avez besoin d'une annulation, utilisez un thread BackgroundWorkerou (plus probablement en dehors de l'interface utilisateur) faites tourner un thread et gardez une référence à celui-ci afin que vous puissiez l'appeler Abort().

piliers7
la source
1
Seulement ... j'espère que l'application est conçue sur une méthode propre pour arrêter une tâche filetée (Abort n'est généralement pas ça)
11

De plus, vous liez un thread de pool de threads pour la durée de vie du travailleur d'arrière-plan, ce qui peut être préoccupant car il n'y en a qu'un nombre fini. Je dirais que si vous ne créez le fil qu'une seule fois pour votre application (et n'utilisez aucune des fonctionnalités du travailleur d'arrière-plan), utilisez un fil de discussion plutôt qu'un fil d'arrière-plan / threadpool.

Mat
la source
1
Je pense que c'est un bon point. Donc, le message que j'en retire est d'utiliser un agent d'arrière-plan si vous avez besoin d'un fil d'arrière-plan "temporairement" pendant la durée de vie du formulaire, mais si vous avez besoin d'un fil d'arrière-plan pour toute la durée de vie du formulaire (qui pourrait être de minutes, heures, days ...) puis utilisez un Thread au lieu de BackgroundWorker afin de ne pas abuser de l'objectif du ThreadPool
freddy smith
Concernant: "... ce qui peut être préoccupant car il n'y en a qu'un nombre fini", voulez-vous dire que d'autres applications sur le système d'exploitation peuvent en avoir besoin et partager à partir du même 'pool'?
Dan W
8

Vous savez, il est parfois plus simple de travailler avec un BackgroundWorker, que vous utilisiez Windows Forms, WPF ou toute autre technologie. La partie intéressante de ces types est que vous obtenez des threads sans avoir à vous soucier trop de l'endroit où vous exécutez les threads, ce qui est idéal pour les tâches simples.

Avant d'utiliser un BackgroundWorkerconsidérez d'abord si vous souhaitez annuler un thread (fermeture de l'application, annulation de l'utilisateur), vous devez décider si votre thread doit vérifier les annulations ou s'il doit être poussé sur l'exécution elle-même.

BackgroundWorker.CancelAsync()se mettra CancellationPendingà truemais ne fera rien de plus, c'est alors la responsabilité des threads de vérifier continuellement cela, gardez à l'esprit également que vous pourriez vous retrouver avec une condition de concurrence dans cette approche où votre utilisateur a annulé, mais le thread s'est terminé avant de tester pour CancellationPending.

Thread.Abort() d'un autre côté lèvera une exception dans l'exécution du thread qui impose l'annulation de ce thread, vous devez cependant faire attention à ce qui pourrait être dangereux si cette exception était soudainement levée pendant l'exécution.

Le threading nécessite un examen très attentif, quelle que soit la tâche, pour une lecture plus approfondie:

Programmation parallèle dans le .NET Framework Managed Threading Meilleures pratiques

Brett Ryan
la source
5

Je savais comment utiliser les threads avant de connaître .NET, il a donc fallu un certain temps pour m'y habituer lorsque j'ai commencé à utiliser BackgroundWorkers. Matt Davis a résumé la différence avec une grande excellence, mais j'ajouterais qu'il est plus difficile de comprendre exactement ce que fait le code, et cela peut rendre le débogage plus difficile. Il est plus facile de penser à créer et à fermer des threads, IMO, que de penser à donner du travail à un pool de threads.

Je ne peux toujours pas commenter les messages des autres, alors pardonnez ma boiterie momentanée en utilisant une réponse pour aborder les piers7

Ne l'utilisez pas à la Thread.Abort();place, signalez un événement et concevez votre thread pour qu'il se termine correctement lorsqu'il vous est signalé. Thread.Abort()lève un ThreadAbortExceptionà un moment arbitraire de l'exécution du thread, ce qui peut faire toutes sortes de choses malheureuses comme des moniteurs orphelins, un état partagé corrompu, etc.
http://msdn.microsoft.com/en-us/library/system.threading.thread.abort.aspx

ajs410
la source
2

Si ce n'est pas cassé - réparez-le jusqu'à ce que ce soit ... je plaisante :)

Mais sérieusement, BackgroundWorker est probablement très similaire à ce que vous avez déjà, si vous l'aviez commencé depuis le début, vous auriez peut-être gagné du temps - mais à ce stade, je n'en vois pas le besoin. À moins que quelque chose ne fonctionne pas ou que vous pensiez que votre code actuel est difficile à comprendre, je m'en tiendrai à ce que vous avez.

Gandalf
la source
2

La différence fondamentale est, comme vous l'avez dit, de générer des événements GUI à partir du BackgroundWorker. Si le thread n'a pas besoin de mettre à jour l'affichage ou de générer des événements pour le thread principal de l'interface graphique, il peut s'agir d'un simple thread.

David R Tribble
la source
2

Je veux souligner un comportement de la classe BackgroundWorker qui n'a pas encore été mentionné. Vous pouvez faire en sorte qu'un Thread normal s'exécute en arrière-plan en définissant la propriété Thread.IsBackground.

Les threads d'arrière-plan sont identiques aux threads de premier plan, sauf que les threads d'arrière-plan n'empêchent pas un processus de se terminer. [ 1 ]

Vous pouvez tester ce comportement en appelant la méthode suivante dans le constructeur de votre fenêtre de formulaire.

void TestBackgroundThread()
{
    var thread = new Thread((ThreadStart)delegate()
    {
        long count = 0;
        while (true)
        {
            count++;
            Debug.WriteLine("Thread loop count: " + count);
        }
    });

    // Choose one option:
    thread.IsBackground = true; // <--- This will make the thread run in background
    thread.IsBackground = false; // <--- This will delay program termination

    thread.Start();
}

Lorsque la propriété IsBackground est définie sur true et que vous fermez la fenêtre, votre application se terminera normalement.

Mais lorsque la propriété IsBackground est définie sur false (par défaut) et que vous fermez la fenêtre, alors seule la fenêtre disparaîtra mais le processus continuera à s'exécuter.

La classe BackgroundWorker utilise un thread qui s'exécute en arrière-plan.

Doomjunky
la source
1

Un travailleur d'arrière-plan est une classe qui fonctionne dans un thread distinct, mais il fournit des fonctionnalités supplémentaires que vous n'obtenez pas avec un simple thread (comme la gestion du rapport de progression des tâches).

Si vous n'avez pas besoin des fonctionnalités supplémentaires fournies par un travailleur en arrière-plan - et il semble que vous n'en ayez pas besoin - alors un thread serait plus approprié.

César
la source
-1

Ce qui me rend perplexe, c'est que le concepteur de studio visuel vous permet uniquement d'utiliser des BackgroundWorkers et des minuteries qui ne fonctionnent pas réellement avec le projet de service.

Il vous donne des commandes de glisser-déposer sur votre service, mais ... n'essayez même pas de le déployer. Ça ne marchera pas.

Services: utilisez uniquement System.Timers.Timer System.Windows.Forms.Timer ne fonctionnera pas même s'il est disponible dans la boîte à outils

Services: BackgroundWorkers ne fonctionnera pas lorsqu'il est exécuté en tant que service Utilisez plutôt System.Threading.ThreadPools ou les appels Async

Scottweeden
la source