web-dev-qa-db-fra.com

WPF Dispatcher.BeginInvoke et threads d'arrière-plan/interface utilisateur

Je pense avoir besoin de quelques éclaircissements sur l'utilisation de WPF Dispatcher.Invoke et Dispatcher.BeginInvoke.

Supposons que je possède un code de travail qui fonctionne depuis longtemps, comme celui qui est appelé à la simple pression d'un bouton dans une application WPF simple:

longWorkTextBox.Text = "Ready For Work!";
Action workAction = delegate
    {
    Console.WriteLine("Starting Work Action");
    int i = int.MaxValue;
    while (i > 0)
        i--;
    Console.WriteLine("Ending Work Action");
    longWorkTextBox.Text = "Work Complete";
    };
longWorkTextBox.Dispatcher.BeginInvoke(DispatcherPriority.Background, workAction);

Ce code verrouille mon interface utilisateur pendant l'exécution de workAction. En effet, Dispatcher appelle toujours s’exécuter sur le fil de l’interface utilisateur, non?

En supposant cela, quelle est la meilleure pratique pour configurer mon répartiteur afin qu'il exécute workAction dans un thread distinct de mon interface utilisateur? Je sais que je peux ajouter un BackgroundWorker à mon workAction pour empêcher le verrouillage de mon interface utilisateur en tant que tel:

longWorkTextBox.Text = "Ready For Work!";
Action workAction = delegate
{
    BackgroundWorker worker = new BackgroundWorker();
    worker.DoWork += delegate
    {
        Console.WriteLine("Starting Slow Work");
        int i = int.MaxValue;
        while (i > 0)
        i--;
        Console.WriteLine("Ending Work Action");
    };
    worker.RunWorkerCompleted += delegate
    {
        longWorkTextBox.Text = "Work Complete";
    };
    worker.RunWorkerAsync();
 };
 longWorkTextBox.Dispatcher.BeginInvoke(DispatcherPriority.Background, workAction);

Existe-t-il des moyens plus élégants de procéder ainsi que d'utiliser BackgroundWorker? J'ai toujours entendu dire que BackgroundWorker est original, je suis donc curieux de connaître certaines alternatives.

18
Matthew Ruston

Honnêtement, je pense que la BackgroundWorker est la solution la plus élégante pour cela. Je ne peux pas penser à un moyen plus simple de le faire.

25
Charlie

Moi aussi, je n'aime pas BackgroundWorker . Une alternative simple peut être quelque chose comme:

using System;
using System.Threading;
using System.Windows;

namespace Sample
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        protected override void OnSourceInitialized(EventArgs e)
        {
             base.OnSourceInitialized(e);
             longWorkTextBox.Text = "Ready For Work!";
       }

        private void startButton_Click(object sender, RoutedEventArgs e)
        {
            new Thread(Work).Start();
        }

        void Work()
        {
            longWorkTextBox.Dispatcher.BeginInvoke((Action)(() => { longWorkTextBox.Text = "Working..."; }));
            Console.WriteLine("Starting Work Action");
            int i = int.MaxValue;
            while (i > 0)
                i--;
            Console.WriteLine("Ending Work Action");
            longWorkTextBox.Dispatcher.BeginInvoke((Action)(() => { longWorkTextBox.Text = "Work Complete"; }));
        }
    }
}

Facile non?

4
italianogrosso

La réponse de Charlie est ce que vous recherchez, vraiment.

Toutefois, si cela est possible, vous pouvez vous demander si vous pouvez fractionner votre travail de manière à ce que les unités de travail individuelles soient petites et n'affectent pas autant l'interface utilisateur. Cela vous permettrait simplement d’utiliser le répartiteur directement. Vous en trouverez un bon exemple sur la page Threading de WPF: https://msdn.Microsoft.com/en-us/library/ms741870%28v=vs.100%29.aspx

4
Anderson Imes

Comme son nom l'indique, il s'exécutera en arrière-plan, vous n'avez donc pas besoin de l'instancier avec Dispatcher. De plus, si vous voulez que ce code soit exécuté sur un WP7, BeginInvoke n'obtient pas le paramètre background.

Ma recommandation est de créer le BackgroundWorker en tant que:

BackgroundWorker worker = new BackgroundWorker;

Et puis créez les gestionnaires pour les événements:

worker.WorkerReportsProgress = true;
worker.WorkerSupportsCancellation = true;
worker.DoWork +=new DoWorkEventHandler(worker_DoWork);
worker.RunWorkerCompleted +=new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
worker.ProgressChanged +=new ProgressChangedEventHandler(worker_ProgressChanged);

Et finalement vous appelez:

bkwkPlayingLoop.RunWorkerAsync();

Il est très tentant d'utiliser Dispatcher depuis DoWork mais d'appeler worker.ReportProgress () et de gérer l'interface utilisateur à partir de là. Sinon, vous devrez faire face à des incohérences lors du déclenchement d’événements de fin.

2
Julio Bailon

Les tâches sont plus faciles à utiliser que les travailleurs en arrière-plan, font plus de choses, ont moins de problèmes et ont été assez bien créées pour que les travailleurs en arrière-plan n'aient plus besoin d'être utilisés ...

0
MattE