[WPF] BackgroundWorker

Dans le cas de la gestion d’interface graphique via WPF, un background worker peut facilite le traitement complexe en fond. Tandis que le thread principal gèrera les intéractions graphiques, le BackgroundWorker lancera son travail sur un thread séparé.

DoWork

Le BackgroundWorker est articulé autour de différents évènement. DoWork en est un, et c’est la méthode abonnée qui ce sera celle qui s’exécutera sur un thread à part.

public MyWindow()
{
   InitializeComponent();
   Thread.CurrentThread.Name = "Thread Principal";
   Console.WriteLine("Constructor: " + Thread.CurrentThread.Name);
   //>Constructor: Thread Principal

   System.ComponentModel.BackgroundWorker backgroundWorker = new System.ComponentModel.BackgroundWorker
   //La méthode apellé sur un thread différent
   backgroundWorker.DoWork += BackgroundWorker_DoWork;
}

private void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
   if (String.IsNullOrEmpty(Thread.CurrentThread.Name))
      Thread.CurrentThread.Name = "Thread BackgroundWorker";

   Console.WriteLine("Constructor: " + Thread.CurrentThread.Name);
   //>Constructor: Thread BackgroundWorker
}

La méthode BackgroundWorker_DoWork ira s’exécuter sur un thread différent.

Attention toutefois : elle ne pourra donc pas manipuler les objets de l’interfaces graphiques : ces derniers appartiennent au thread principal. Pour manipuler ces objets il faudra le faire via les autres évènements du BackgroundWorker

Changed et Completed

La méthode liée à l’évènement DoWork et la seule à être exécutée sur un différent thread. Les autres seront lancée sur le thread principal et pourront facilement intervenir sur n’importe quel objet de l’UI.

  • ProgressChanged intervient lorsque le backgroundWorker notifie explicitement un changement (via ReportProgress(int value)). Il faut par contre que le BackgroundWorker soit autorisé à faire ce signalement (WorkerReportsProgress= true).
  • RunWorkerCompleted intervient automatiquement lorsque la méthode abonné à DoWork s’est terminée, ou lorsque le backgroundWorker a été interrompu (e.Cancelled == true) à condition ici qu’il soit autorisé à être interrompu (WorkerSupportsCancellation = true).
MyWindow(){

   //(...)
   backgroundWorker.WorkerReportsProgress= true;
   backgroundWorker.WorkerSupportsCancellation = true;

   backgroundWorker.ProgressChanged += BackgroundWorker_ProgressChanged;
   backgroundWorker.RunWorkerCompleted += BackgroundWorker_RunWorkerCompleted;

   if (BackgroundWorker.IsBusy == false)
      BackgroundWorker.RunWorkerAsync();
}

//(...)

private void BackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
   Console.WriteLine("BackgroundWorker_RunWorkerCompleted : " + Thread.CurrentThread.Name);            
   //> BackgroundWorker_RunWorkerCompleted : Thread Principal

   if (e.Cancelled == true)
   {
      string state = "Canceled";
   }
}

private void BackgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
   Console.WriteLine("BackgroundWorker_ProgressChanged : " + Thread.CurrentThread.Name);
   //> BackgroundWorker_ProgressChanged : Thread Principal

   int value = e.ProgressPercentage;
}

 

Exemple : Loading

On peut ainsi facilement envisager un BackgroundWorker qui chargerait les données sur une interface, laissant quand même l’utilisateur l’occasion d’afficher les paramètre de l’application et de la modifier.

private void loadWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
   if (e.Cancelled == true)
   {
      LoadingState = "Canceled";
   }
   else if (e.Error != null)
   {
      LoadingState = "Error : " + e.Error.Message;
   }
   else
      LoadingState = "Loading Complete";
}

private void loadWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
   LoadingState = "Loading...";
   Pourcentage = e.ProgressPercentage;
}

private void loadWorker_DoWork(object sender, DoWorkEventArgs e)
{
   System.ComponentModel.BackgroundWorker loadWorker = sender as System.ComponentModel.BackgroundWorker;

   for (int i = 0; i <= 10; i++)
      if (loadWorker.CancellationPending)
      {
         e.Cancel = true;
         break;
      }
      else
      {
         //Traitements 
         Thread.Sleep(500);
         loadWorker.ReportProgress(i * 10);
      }
}

private void CancelBtn_Click(object sender, RoutedEventArgs e)
{
   if (loadWorker.WorkerSupportsCancellation)
      loadWorker.CancelAsync();
}

Ici le bouton d’annulation permettra de stopper le traitement du second thread. Ce dernier, au fur et à mesure de son traitement, notifiera le tread principal de son avancé. Les propriété Bindées Pourcentage et LoadingState afficheront quand à elle l’état d’avancement du chargement.

Sources
  • https://docs.microsoft.com/fr-fr/dotnet/api/system.componentmodel.backgroundworker?view=netframework-4.8
  • https://webman.developpez.com/articles/dotnet/introbackroundworker/

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *