2010-02-18 7 views
4

J'ai un thread principal, et de nombreux autres threads d'arrière-plan. L'utilisation principale de ces threads d'arrière-plan consiste à interroger des données (beaucoup de requêtes sur le Web, c'est pourquoi je crée plusieurs threads: pour éviter le retard de l'interface utilisateur).C# Attendre que tous les threads se terminent dans ThreadPool

Quand il s'agit d'exporter les données dans le thread principal (l'interface utilisateur), j'ai besoin d'attendre que tous les autres threads sont terminés.

Mon code est:

//...code to open save file dialog... 

//this loop is to wait for all the threads finish their query 
//QueryThread.threadCount is the count of the background threads 
while (QueryThread.threadCount != 0) 
{ 
    Thread.CurrentThread.Join(1000); 
    Console.WriteLine(QueryThread.threadCount); 
} 

//...code to export data... 

Si je commente la boucle while sur, le programme se déroulera sans heurts, mais certains de mes données exportées ont la possibilité de montrer certains matériaux « indésirables » puisque certains d'entre l'arrière-plan les discussions n'ont pas terminé leur travail.

Cependant, la boucle while ci-dessus est infinie, le threadCount ne change jamais, ce qui signifie que lors de la méthode "Join()", aucun thread d'arrière-plan n'est en cours d'exécution.

Pourquoi les threads d'arrière-plan sont-ils bloqués et comment puis-je résoudre le problème?

Merci beaucoup!

Répondre

2

Votre implémentation est incorrecte et vous ne devez pas utiliser Join comme primitive de synchronisation entre vos threads.

Ce que vous devez faire est implémenter le producer-consumer pattern. Cela vous permettra d'avoir les threads en attente de travail, puis viendra en vie pour faire ce travail lorsque vous le placez dans la file d'attente.

Cependant, la modification que je ferais est que, à partir du thread UI, n'ajoutez pas les données directement à la file d'attente que le producteur et le consommateur partagent. Au lieu de cela, faites un copier de ces données, puis mettez que dans la file d'attente.

Pour plus d'informations sur la façon de mettre en œuvre le modèle producteur-consommateur dans .NET, je vous suggère de lire la documentation MSDN intitulé « How to: Synchronize a Producer and a Consumer Thread (C# Programming Guide) »

+0

Merci. Je me sens aussi bizarre de ma mise en œuvre, mais je suis nouveau à la programmation de threads. Réjouissez-vous de voir votre suivi. =] –

+0

Bon liens, mais cela ne semble pas être son problème - je pense que son problème est plus sur l'agrégation de plusieurs appels de données, puis le pompage des résultats dans le fil de l'interface utilisateur une fois qu'ils ont tous terminé? – slugster

3

Vous appelez la méthode Join sur le thread courant qui ne fait pas beaucoup de sens. Vous devriez l'appeler sur vos threads de travail:

foreach (Thread thread in workerThreads) 
{ 
    thread.Join(1000); 
} 

Malheureusement, ce genre de défaites le but d'utiliser des fils, car il bloque l'appel jusqu'à ce que tous les autres fils sont terminés.

L'événement RunWorkerCompleted d'un BackgroundWorker peut être utilisé pour notifier l'achèvement d'une tâche d'arrière-plan et effectuer des mises à jour sur le formulaire.

+0

J'utilise ThreadPool pour ces threads d'arrière-plan Comment faire la méthode foreach à ce sujet? merci –

2

Je pense que vous voulez regarder dans la signalisation. Avoir des signaux (ManualResetEvent/AutoResetEvent) pour vos threads. Set() le handle de signal associé dans le thread de travail quand c'est fait. Dans le thread principal, faites un `WaitAll (signal1, signal2, signal3) 'pour attendre l'achèvement de vos threads de travail.

Hope this helps,

+0

J'ai essayé cela mais il dit que le fil principal est un STAThread qui ne supporte pas WaitAll –

+0

alors vous devez les itérer et WaitOne sur chaque - l'ordre n'a pas d'importance – Marek

1

je ne pouvais pas résister à essayer un peu moi-même. Je suis sûr qu'il y a de la place pour l'amélioration, mais je pense que cela montre comment faire face à certains problèmes de multi-threading, y compris la question originale.

Form.cs

 

namespace STAFormWithThreadPoolSync 
{ 
    internal delegate void WorkerEvent(WorkerEventInfo info); 

    public partial class Form1 : Form 
    { 
     // We'll create a state object for each worker process 
     List<WorkerState> workerStates = new List<WorkerState>(); 

     public Form1() 
     { 
      InitializeComponent(); 
     } 

     // Executed in the main thread 
     private void button1_Click(object sender, EventArgs e) 
     { 
      workersList.Items.Clear(); 

      // Read the amount of thread we should start from the form 
      int threadCountToUse = (int)ThreadCount.Value; 

      WorkerEvent woEvent = new WorkerEvent(this.workerEventOccured); 

      // Start up all threads 
      for (int counter = 0; counter < threadCountToUse; ++counter) 
      { 
       // An object we can pass values into for the worker process to use. 
       WorkerState workerState = new WorkerState(); 

       workerState.OnStarted += woEvent; 
       workerState.OnFinished += woEvent; 

       // Register for the signal (and store its registered wait handle in the stateObj, which we also pass into the parameters!) 
       workerState.registeredWaitHandle = ThreadPool.RegisterWaitForSingleObject(workerState.finishSignal, this.ItemHasFinished, workerState, -1, true); 

       // Store the state object for later use. 
       workerStates.Add(workerState); 
      } 

      WorkersProgress.Minimum = 0; 
      WorkersProgress.Maximum = workerStates.Count; 

      workerStates.ForEach(workerState => 
       { 
        // Fire of the worker thread (with the state object) 
        ThreadPool.QueueUserWorkItem(this.ProcessItem, workerState); 
       } 
      ); 



      button1.Enabled = false; 
      CurrentResult.Value = 0; 
      CurrentResultLabel.Text = "Current value"; 
      ProgressTimer.Start(); 
     } 

     // event is run on the callers thread, so carefull accessing our controls on our form. 
     internal void workerEventOccured(WorkerEventInfo info) 
     { 
      if (this.workersList.InvokeRequired) 
      { 
       WorkerEvent workerEvent = new WorkerEvent(workerEventOccured); 
       this.Invoke(workerEvent, new object[] { info }); 
      } 
      else 
      { 
       switch (info.eventType) 
       { 
        case EventType.WorkerStarted: 
         this.workersList.Items.Add(String.Format("Worker started on thread : {0}", info.workerState.threadId)); 
         break; 

        case EventType.WorkerEnded: 
         this.workersList.Items.Add(String.Format("Worker finished on thread : {0}", info.workerState.threadId)); 
         break; 
        case EventType.AllWorkersFinished: 
         this.workersList.Items.Add("ALL workers finished"); 
         ProgressTimer.Stop(); 
         button1.Enabled = true; 
         CurrentResultLabel.Text = "Final value"; 
         break; 
       } 
      } 
     } 

     // Executed in threadpool thread. 
     private void ProcessItem(object state) 
     { 
      WorkerState workerState = state as WorkerState; 
      int threadId = Thread.CurrentThread.ManagedThreadId; 
      workerState.threadId = threadId.ToString(); 

      WorkerEventInfo weInfo = new WorkerEventInfo(); 
      weInfo.eventType = EventType.WorkerStarted; 
      weInfo.workerState = workerState; 
      workerState.Started(weInfo); 

      // Simulate work for ((threadid/2) seconds. 
      Thread.Sleep((threadId * 500)); 

      // Set the result in the state object to the threadId; 
      workerState.result = threadId; 

      // Signal that this thread is done. 
      workerState.finishSignal.Set(); 
     } 

     // Executed in threadpool thread 
     private void ItemHasFinished(object state, bool timedOut) 
     { 
      // get our state object 
      WorkerState workerState = state as WorkerState; 

      WorkerEventInfo weInfo = new WorkerEventInfo(); 
      weInfo.eventType = EventType.WorkerEnded; 
      weInfo.workerState = workerState; 

      workerState.Finished(weInfo); 
     } 

     private void ProgressTimer_Tick(object sender, EventArgs e) 
     { 
      List<WorkerState> removeStates = new List<WorkerState>(); 
      workerStates.ForEach(workerState => 
       { 
        if (workerState.finishSignal.WaitOne(0)) 
        { 
         CurrentResult.Value += workerState.result; 
         removeStates.Add(workerState); 
        } 
       } 
      ); 

      removeStates.ForEach(workerState => 
       { 
        workerState.registeredWaitHandle.Unregister(workerState.finishSignal); 
        workerStates.Remove(workerState); 
       } 
      ); 


      WorkersProgress.Value = workerStates.Count; 
      if (workerStates.Count == 0) 
      { 
       WorkerEventInfo weInfo = new WorkerEventInfo(); 
       weInfo.eventType = EventType.AllWorkersFinished; 
       weInfo.workerState = null; 
       this.workerEventOccured(weInfo); 
      } 

     } 
    } 

    internal class WorkerState 
    { 
     internal string threadId = ""; 
     internal int result = 0; 
     internal RegisteredWaitHandle registeredWaitHandle = null; 
     internal AutoResetEvent finishSignal = new AutoResetEvent(false); 
     internal event WorkerEvent OnStarted = new WorkerEvent((info) => {}); 
     internal event WorkerEvent OnFinished = new WorkerEvent((info) => { }); 

     internal void Started(WorkerEventInfo info) 
     { 
      OnStarted(info); 
     } 

     internal void Finished(WorkerEventInfo info) 
     { 
      OnFinished(info); 
      this.finishSignal.Set(); 
     } 
    } 

    internal enum EventType 
    { 
     WorkerStarted, 
     WorkerEnded, 
     AllWorkersFinished 
    } 

    internal class WorkerEventInfo 
    { 
     internal EventType eventType; 
     internal WorkerState workerState; 
    } 
} 

 

Form.Designer.cs

 

namespace STAFormWithThreadPoolSync 
{ 
    partial class Form1 
    { 
     /// 
     /// Required designer variable. 
     /// 
     private System.ComponentModel.IContainer components = null; 

     /// 
     /// Clean up any resources being used. 
     /// 
     /// true if managed resources should be disposed; otherwise, false. 
     protected override void Dispose(bool disposing) 
     { 
      if (disposing && (components != null)) 
      { 
       components.Dispose(); 
      } 
      base.Dispose(disposing); 
     } 

     #region Windows Form Designer generated code 

     /// 
     /// Required method for Designer support - do not modify 
     /// the contents of this method with the code editor. 
     /// 
     private void InitializeComponent() 
     { 
      this.components = new System.ComponentModel.Container(); 
      this.button1 = new System.Windows.Forms.Button(); 
      this.ThreadCount = new System.Windows.Forms.NumericUpDown(); 
      this.workersList = new System.Windows.Forms.ListView(); 
      this.WorkerProcessColumn = new System.Windows.Forms.ColumnHeader(); 
      this.ProgressTimer = new System.Windows.Forms.Timer(this.components); 
      this.WorkersProgress = new System.Windows.Forms.ProgressBar(); 
      this.CurrentResultLabel = new System.Windows.Forms.Label(); 
      this.CurrentResult = new System.Windows.Forms.NumericUpDown(); 
      this.label2 = new System.Windows.Forms.Label(); 
      ((System.ComponentModel.ISupportInitialize)(this.ThreadCount)).BeginInit(); 
      ((System.ComponentModel.ISupportInitialize)(this.CurrentResult)).BeginInit(); 
      this.SuspendLayout(); 
      // 
      // button1 
      // 
      this.button1.Location = new System.Drawing.Point(212, 19); 
      this.button1.Name = "button1"; 
      this.button1.Size = new System.Drawing.Size(93, 23); 
      this.button1.TabIndex = 0; 
      this.button1.Text = "Start threads"; 
      this.button1.UseVisualStyleBackColor = true; 
      this.button1.Click += new System.EventHandler(this.button1_Click); 
      // 
      // ThreadCount 
      // 
      this.ThreadCount.Location = new System.Drawing.Point(23, 21); 
      this.ThreadCount.Minimum = new decimal(new int[] { 
      2, 
      0, 
      0, 
      0}); 
      this.ThreadCount.Name = "ThreadCount"; 
      this.ThreadCount.Size = new System.Drawing.Size(183, 20); 
      this.ThreadCount.TabIndex = 1; 
      this.ThreadCount.Value = new decimal(new int[] { 
      4, 
      0, 
      0, 
      0}); 
      // 
      // workersList 
      // 
      this.workersList.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { 
      this.WorkerProcessColumn}); 
      this.workersList.Location = new System.Drawing.Point(23, 80); 
      this.workersList.Name = "workersList"; 
      this.workersList.Size = new System.Drawing.Size(486, 255); 
      this.workersList.TabIndex = 3; 
      this.workersList.UseCompatibleStateImageBehavior = false; 
      this.workersList.View = System.Windows.Forms.View.Details; 
      // 
      // WorkerProcessColumn 
      // 
      this.WorkerProcessColumn.Text = "Worker process"; 
      this.WorkerProcessColumn.Width = 482; 
      // 
      // ProgressTimer 
      // 
      this.ProgressTimer.Interval = 200; 
      this.ProgressTimer.Tick += new System.EventHandler(this.ProgressTimer_Tick); 
      // 
      // WorkersProgress 
      // 
      this.WorkersProgress.Location = new System.Drawing.Point(112, 341); 
      this.WorkersProgress.Name = "WorkersProgress"; 
      this.WorkersProgress.Size = new System.Drawing.Size(397, 24); 
      this.WorkersProgress.TabIndex = 4; 
      // 
      // CurrentResultLabel 
      // 
      this.CurrentResultLabel.AutoSize = true; 
      this.CurrentResultLabel.Location = new System.Drawing.Point(578, 266); 
      this.CurrentResultLabel.Name = "CurrentResultLabel"; 
      this.CurrentResultLabel.Size = new System.Drawing.Size(74, 13); 
      this.CurrentResultLabel.TabIndex = 5; 
      this.CurrentResultLabel.Text = "Current Result"; 
      // 
      // CurrentResult 
      // 
      this.CurrentResult.Location = new System.Drawing.Point(581, 282); 
      this.CurrentResult.Maximum = new decimal(new int[] { 
      -1593835520, 
      466537709, 
      54210, 
      0}); 
      this.CurrentResult.Name = "CurrentResult"; 
      this.CurrentResult.ReadOnly = true; 
      this.CurrentResult.Size = new System.Drawing.Size(169, 20); 
      this.CurrentResult.TabIndex = 6; 
      // 
      // label2 
      // 
      this.label2.AutoSize = true; 
      this.label2.Location = new System.Drawing.Point(25, 352); 
      this.label2.Name = "label2"; 
      this.label2.Size = new System.Drawing.Size(81, 13); 
      this.label2.TabIndex = 7; 
      this.label2.Text = "processing load"; 
      // 
      // Form1 
      // 
      this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 
      this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 
      this.ClientSize = new System.Drawing.Size(762, 377); 
      this.Controls.Add(this.label2); 
      this.Controls.Add(this.CurrentResult); 
      this.Controls.Add(this.CurrentResultLabel); 
      this.Controls.Add(this.WorkersProgress); 
      this.Controls.Add(this.workersList); 
      this.Controls.Add(this.ThreadCount); 
      this.Controls.Add(this.button1); 
      this.Name = "Form1"; 
      this.Text = "Form1"; 
      ((System.ComponentModel.ISupportInitialize)(this.ThreadCount)).EndInit(); 
      ((System.ComponentModel.ISupportInitialize)(this.CurrentResult)).EndInit(); 
      this.ResumeLayout(false); 
      this.PerformLayout(); 

     } 

     #endregion 

     private System.Windows.Forms.Button button1; 
     private System.Windows.Forms.NumericUpDown ThreadCount; 
     private System.Windows.Forms.ListView workersList; 
     private System.Windows.Forms.ColumnHeader WorkerProcessColumn; 
     private System.Windows.Forms.Timer ProgressTimer; 
     private System.Windows.Forms.ProgressBar WorkersProgress; 
     private System.Windows.Forms.Label CurrentResultLabel; 
     private System.Windows.Forms.NumericUpDown CurrentResult; 
     private System.Windows.Forms.Label label2; 
    } 
} 
 

Hope this helps,

+0

Merci! vous montriez certainement votre passion sur la programmation =] Mais STH est manquant dans le code: 1. boucle dans button1_Click 2. ForEach dans la ProgressTimer_Tick –

+0

Correction du problème pour button1-cliquez pas correctement montrant la source. N'a pas vu quelque chose de mal avec ProgressTimer, son utilisation de lambda. –

0

je résolu le problème en changeant mon approche de modèle producteur-consommateur.

Merci à tous. S'il vous plaît regarder ce link (fourni par casperOne ci-dessus), mais soyez prudence ne suivent pas la mise en œuvre de Microsoft ....

Go here à la place vous donnera une meilleure réponse.

Bien sûr, j'ai apporté quelques modifications, le type de la file d'attente est délégué dans mon cas.

public static class QueryThread 
{ 
    private static SyncEvents _syncEvents = new SyncEvents(); 
    private static Queue<Delegate> _queryQueue = new Queue<Delegate>(); 

    static Producer queryProducer; 
    static Consumer queryConsumer; 

    public static void init() 
    { 
     queryProducer = new Producer(_queryQueue, _syncEvents); 
     queryConsumer = new Consumer(_queryQueue, _syncEvents); 

     Thread producerThread = new Thread(queryProducer.ThreadRun); 
     Thread consumerThread = new Thread(queryConsumer.ThreadRun); 

     producerThread.IsBackground = true; 
     consumerThread.IsBackground = true; 

     producerThread.Start(); 
     consumerThread.Start(); 
    } 

    public static void Enqueue(Delegate item) 
    { 
     queryQueue.Enqueue(item); 
    } 
} 

Lorsqu'une requête est nécessaire dans le fil conducteur, Enqueue un des points de déléguer à la fonction qui effectue la requête en appelant la Enqueue (point délégué). Cela ajoute un délégué à la file "privée" du producteur.

Le producteur ajoutera les éléments dans sa propre file d'attente à la file d'attente partagée à l'occasion appropriée (comme générer un nombre aléatoire et le mettre dans la file d'attente partagée dans l'exemple msdn).

Le consommateur supprime les délégués et les exécute.

Merci à tous pour votre aide. =]

Questions connexes