2010-02-18 20 views
4

我有一個主線程和許多其他後臺線程。C#等到所有線程在ThreadPool中終止

這些後臺線程的主要用途是查詢數據(來自web的許多查詢,這就是爲什麼我創建多個線程:爲了避免滯後用戶界面)。

當涉及到在主線程(用戶界面)中導出數據時,我需要等到所有其他線程完成。

我的代碼是:

//...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... 

如果我評論while循環時,程序將平穩運行,但我的一些出口數據將顯示,因爲一些背景的一些「無用」的材料的可能性線程還沒有完成他們的工作。

但是,上面的while循環是無限的,threadCount永遠不會改變,這意味着在「Join()」方法期間,沒有後臺線程正在運行。

爲什麼後臺線程被阻塞,我該如何解決問題?

非常感謝!

回答

2

你的實現是錯誤的,你不應該在你的線程之間使用Join作爲同步原語。

你應該做的是實現producer-consumer pattern。這將允許您讓線程等待工作,然後在您將其放入隊列中時執行該工作。

但是,我所做的更改是,從UI線程中,不要將數據直接添加到生產者和消費者共享的隊列中。相反,製作一個複製該數據,然後將放入隊列中。

有關如何使用.NET實現生產者 - 消費者模式的更多信息,我建議你閱讀標題爲MSDN文檔「How to: Synchronize a Producer and a Consumer Thread (C# Programming Guide)

+0

謝謝。 我也覺得我的實現很奇怪,但我是線程編程的新手。 期待看到你的後續行動。 =] –

+0

良好的鏈接,但這似乎並不是他的問題 - 我認爲他的問題更多的是聚合多個數據調用,然後將結果一旦完成後將結果返回給UI線程? – slugster

3

您所呼叫的當前線程的Join方法不使很有道理。你應該把它在你的工作線程:

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

不幸的是這種失敗的使用線程因爲直到所有其他線程完成它會阻塞調用的目的。

RunWorkerCompleted事件BackgroundWorker可用於通知完成後臺任務並在窗體上執行更新。

+0

我正在使用ThreadPool來處理那些後臺線程 如何對其執行foreach方法?謝謝 –

2

我想你想看看信號。爲您的線程發出信號(ManualResetEvent/AutoResetEvent)。設置()工作線程中的關聯信號句柄。在主線程中執行`WaitAll(signal1,signal2,signal3)'等待工作線程完成。

希望這會有所幫助,

+0

我已經試過這個,但它說主線程是一個STAThread,它不支持WaitAll –

+0

那麼你必須迭代它們和WaitOne - 訂單無關緊要 – Marek

1

我無法拒絕嘗試一些我自己。我確信有改進的空間,但我認爲它展示瞭如何處理一些多線程問題,包括原始問題。

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; 
    } 
} 
 

希望這有助於

+0

謝謝!你絕對錶現出你對編程的熱情=] 但是......代碼中缺少...... 1. for循環in button1_Click 2. forEach in ProgressTimer_Tick –

+0

修復了按鈕1-點擊的問題,不能正確顯示源代碼。 ProgressTimer沒有發現任何問題,它使用lambda的。 –

0

我改變我的做法,以生產者 - 消費者模式解決了這個問題。

謝謝大家。 請看這link(由casperOne提供),但請注意不要執行微軟...

here反而會給你一個更好的答案。

當然,我已經做了一些改變,隊列的類型是委託我的情況。

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); 
    } 
} 

當需要在主線程的查詢,排隊的委託指向調用排隊(委託項目)使得查詢功能。這將一個委託添加到生產者的「私人」隊列中。

生產者將在合適的場合將自己隊列中的項目添加到共享隊列(例如生成隨機數並將其放入msdn示例中的共享隊列中)。

消費者離開代表並運行它們。

謝謝大家的幫助。 =]