52 votes

Comment mettre à jour l'interface graphique avec le travail en arrière-plan ?

J'ai passé toute la journée à essayer de faire en sorte que mon application utilise des threads, mais sans succès. J'ai lu beaucoup de documentation à ce sujet et j'obtiens toujours beaucoup d'erreurs, alors j'espère que vous pourrez m'aider.

J'ai une méthode qui prend beaucoup de temps et qui appelle la base de données et met à jour l'interface graphique. Cela doit se produire en permanence (ou toutes les 30 secondes environ).

public class UpdateController
{
    private UserController _userController;

    public UpdateController(LoginController loginController, UserController userController)
    {
        _userController = userController;
        loginController.LoginEvent += Update;
    }

    public void Update()
    {
        BackgroundWorker backgroundWorker = new BackgroundWorker();
        while(true)
        {
            backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
            backgroundWorker.RunWorkerAsync();
        }     
    }

    public void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
    {
        _userController.UpdateUsersOnMap();
    }
}

Avec cette approche, j'obtiens une exception parce que le backgroundworker n'est pas un thread STA (mais d'après ce que j'ai compris, c'est ce que je devrais utiliser). J'ai essayé avec un thread STA et cela a donné d'autres erreurs.

Je pense que le problème vient du fait que j'essaie de mettre à jour l'interface graphique tout en effectuant l'appel de la base de données (dans le fil d'arrière-plan). Je devrais seulement faire l'appel de la base de données et ensuite, d'une manière ou d'une autre, revenir au thread principal. Après l'exécution du thread principal, il devrait revenir au thread d'arrière-plan et ainsi de suite. Mais je ne vois pas comment faire cela.

L'application devrait mettre à jour l'interface graphique juste après l'appel à la base de données. Les événements Firering ne semblent pas fonctionner. Le thread d'arrière-plan ne fait que les saisir.

EDITAR:

De très bonnes réponses :) Voici le nouveau code :

public class UpdateController{
private UserController _userController;
private BackgroundWorker _backgroundWorker;

public UpdateController(LoginController loginController, UserController userController)
{
    _userController = userController;
    loginController.LoginEvent += Update;
    _backgroundWorker = new BackgroundWorker();
    _backgroundWorker.DoWork += backgroundWorker_DoWork;
    _backgroundWorker.RunWorkerCompleted += backgroundWorker_RunWorkerCompleted;
}

public void _backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    _userController.UpdateUsersOnMap();
}

public void Update()
{   
    _backgroundWorker.RunWorkerAsync();
}

void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    //UI update
    System.Threading.Thread.Sleep(10000);
    Update();
}

public void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
    // Big database task
}

}

Mais comment puis-je faire en sorte que cela fonctionne toutes les 10 secondes ? System.Threading.Thread.Sleep(10000) ne fera que figer mon interface graphique et la boucle while(true) dans Update() comme suggéré donne une exception (Thread trop occupé).

2 votes

Vous ne pouvez pas appeler Sleep sur le thread de l'interface utilisateur - vous devez utiliser une minuterie, plus précisément DispatcherTimer.

0 votes

Pourquoi pokez-vous le thread de l'interface utilisateur quand il y a un BackgroundWorker ? :) Et je suis très heureux que vous ayez posé cette question avec beaucoup d'efforts car elle me/nous profite aujourd'hui.

0 votes

J'avais des problèmes avec la progression du rapport. Il est important de "_backgroundWorker.ReportProgress(p, param) ;" dans DoWork().

51voto

kiwipom Points 5336

Vous devez déclarer et configurer le BackgroundWorker une fois - puis invoquer la méthode RunWorkerAsync dans votre boucle...

public class UpdateController
{
    private UserController _userController;
    private BackgroundWorker _backgroundWorker;

    public UpdateController(LoginController loginController, UserController userController)
    {
        _userController = userController;
        loginController.LoginEvent += Update;
        _backgroundWorker = new BackgroundWorker();
        _backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
        _backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged);
        _backgroundWorker.WorkerReportsProgress= true;
    }

    public void Update()
    {
         _backgroundWorker.RunWorkerAsync();    
    }

    public void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
    {
        while (true)
        {
        // Do the long-duration work here, and optionally
        // send the update back to the UI thread...
        int p = 0;// set your progress if appropriate
        object param = "something"; // use this to pass any additional parameter back to the UI
        _backgroundWorker.ReportProgress(p, param);
        }
    }

    // This event handler updates the UI
    private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        // Update the UI here
//        _userController.UpdateUsersOnMap();
    }
}

0 votes

Cela déclenche une exception. "Ce BackgroundWorker est actuellement occupé et ne peut pas exécuter plusieurs tâches simultanément." :(

0 votes

Désolé - oui, c'est vrai, désolé ! Veuillez voir la mise à jour - j'ai déplacé la boucle while dans l'instance unique de la méthode 'DoWork' ; cela vous permettra de pousser les notifications de l'interface utilisateur via 'ReportProgress', et ensuite si vous le souhaitez, vous pouvez ajouter un gestionnaire de méthode pour le 'Completed' si vous voulez un jour sortir de votre boucle infinie... merci :)

0 votes

... et vous pouvez envoyer ce thread de travail à Sleep() pendant 10 secondes sans que le thread de l'interface utilisateur ne soit affecté !

11voto

mjmarsh Points 5858

Vous devez utiliser le Contrôle.InvokeRequired pour déterminer si vous êtes sur un fil de fond. Vous devez ensuite invoquer votre logique qui a modifié votre interface utilisateur via la propriété Control.Invoke pour forcer vos opérations d'interface utilisateur à se produire sur le thread principal. Pour ce faire, vous créez un délégué et vous le passez à la méthode Control.Invoke méthode. Le problème ici est que vous avez besoin d'un objet dérivé de Contrôle pour appeler ces méthodes.

Modifier : Comme un autre utilisateur l'a posté, si yo vous pouvez attendre le BackgroundWorker.Completed pour mettre à jour votre interface utilisateur, vous pouvez vous abonner à cet événement et appeler directement votre code d'interface utilisateur. BackgroundWorker_Completed est appelé sur le thread principal de l'application. Mon code suppose que vous voulez faire des mises à jour pendant l'opération. Une alternative à ma méthode est de souscrire à l'événement BwackgroundWorker.ProgressChanged mais je pense que vous devrez toujours appeler Appeler pour mettre à jour votre interface utilisateur dans ce cas.

par exemple

public class UpdateController
{
    private UserController _userController;        
    BackgroundWorker backgroundWorker = new BackgroundWorker();

    public UpdateController(LoginController loginController, UserController userController)
    {
        _userController = userController;
        loginController.LoginEvent += Update;
    }

    public void Update()
    {                        
         // The while loop was unecessary here
         backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
         backgroundWorker.RunWorkerAsync();                 
    }

    public delegate void DoUIWorkHandler();

    public void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
    {
       // You must check here if your are executing on a background thread.
       // UI operations are only allowed on the main application thread
       if (someControlOnMyForm.InvokeRequired)
       {
           // This is how you force your logic to be called on the main
           // application thread
           someControlOnMyForm.Invoke(new             
                      DoUIWorkHandler(_userController.UpdateUsersOnMap);
       }
       else
       {
           _userController.UpdateUsersOnMap()
       }
    }
}

1 votes

Je pense que j'ai fait cette application de manière assez maladroite. J'ai des objets d'interface utilisateur à l'intérieur de mes objets de contrôle. Je ne suis donc pas sûr que cela corresponde à mon programme. Je pense qu'il y aura aussi beaucoup de changements si je fais cela.

0 votes

Étant donné que la question originale est étiquetée wpf je trouve cette réponse complètement inapplicable.

5voto

µBio Points 6959

Vous devriez supprimer le while(true), vous ajoutez une infinité de gestionnaires d'événements et les invoquez une infinité de fois.

4voto

Lee Points 63849

Vous pouvez utiliser l'événement RunWorkerCompleted de la classe backgroundWorker pour définir ce qui doit être fait lorsque la tâche d'arrière-plan est terminée. Vous devez donc effectuer l'appel à la base de données dans le gestionnaire DoWork, puis mettre à jour l'interface dans le gestionnaire RunWorkerCompleted, comme ceci :

BackgroundWorker bgw = new BackgroundWorker();
bgw.DoWork += (o, e) => { longRunningTask(); }

bgw.RunWorkerCompleted += (o, e) => {
    if(e.Error == null && !e.Cancelled)
    {
        _userController.UpdateUsersOnMap();
    }
}

bgw.RunWorkerAsync();

4voto

morpheus Points 2391

En plus des commentaires précédents, jetez un coup d'œil à www.albahari.com/threading - Le meilleur document sur l'enfilage que vous trouverez jamais. Il vous apprendra à utiliser correctement le BackgroundWorker.

Vous devez mettre à jour l'interface graphique lorsque le BackgroundWorker déclenche l'événement Completed (qui est invoqué sur le thread de l'interface graphique pour vous faciliter la tâche, afin que vous n'ayez pas à faire Control.Invoke vous-même).

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