簡體   English   中英

生成多個線程進行工作,然后等待所有線程完成

[英]Spawn Multiple Threads for work then wait until all finished

只需要有關多線程任務的“最佳實踐”的一些建議。

例如,我們有一個C#應用程序,該應用程序在啟動時會從數據庫中各種“類型”表中讀取數據,並將該信息存儲在一個傳遞給應用程序的集合中。 這樣可以防止我們每次需要此信息時都訪問數據庫。

目前,應用程序正在同步從10個表中讀取數據。 我真的很想讓應用程序從不同線程中的每個表中讀取,它們都並行運行。 應用程序將等待所有線程完成,然后再繼續啟動應用程序。

我已經研究了BackGroundWorker,但只希望獲得有關完成上述操作的一些建議。

  1. 為了加快應用程序的啟動時間,該方法聽起來是否合乎邏輯?
  2. 考慮到每個線程的工作彼此獨立,我們如何最好地處理所有線程,我們只需要等待所有線程完成就可以繼續。

我期待一些答案

我的首選是通過一個WaitHandle處理此問題,並使用Interlocked避免鎖定計數器:

class Program
{
    static void Main(string[] args)
    {
        int numThreads = 10;
        ManualResetEvent resetEvent = new ManualResetEvent(false);
        int toProcess = numThreads;

        // Start workers.
        for (int i = 0; i < numThreads; i++)
        {
            new Thread(delegate()
            {
                Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
                // If we're the last thread, signal
                if (Interlocked.Decrement(ref toProcess) == 0)
                    resetEvent.Set();
            }).Start();
        }

        // Wait for workers.
        resetEvent.WaitOne();
        Console.WriteLine("Finished.");
    }
}

這很好用,並且可以擴展到任意數量的線程處理,而不會引入鎖定。

我喜歡@Reed的解決方案。 在.NET 4.0中完成此操作的另一種方法是使用CountdownEvent

class Program
{
    static void Main(string[] args)
    {
        var numThreads = 10;
        var countdownEvent = new CountdownEvent(numThreads);

        // Start workers.
        for (var i = 0; i < numThreads; i++)
        {
            new Thread(delegate()
            {
                Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
                // Signal the CountdownEvent.
                countdownEvent.Signal();
            }).Start();
        }

        // Wait for workers.
        countdownEvent.Wait();
        Console.WriteLine("Finished.");
    }
}

如Mark所說,如果您有64個以上的STA線程等待句柄。 您可以使用線程創建一個列表,然后等待所有線程在第二個循環中完成。

//check that all threads have completed.
foreach (Thread thread in threadList)
{
     thread.Join();

}  

如果您不在.NET 4.0上,則可以使用List < ManualResetEvent >,每個線程一個,然后等待它們被設置 要在多個線程上等待,您可以考慮使用WaitAll,但要注意64個等待句柄的限制。 如果您需要的還不止這些,您可以遍歷它們,然后逐個等待。

如果您想要更快的啟動體驗,則可能不需要等待啟動期間讀取所有數據。 只需顯示GUI,任何丟失的信息都可以通過某種“正在更新...”圖標或類似的圖標以灰色顯示。 當信息進入時,只需觸發一個事件來更新GUI。 甚至在讀取所有表中的所有數據之前,用戶可能會開始執行許多操作。

如果您喜歡冒險,可以使用C#4.0和Task Parallel Library:

Parallel.ForEach(jobList, curJob => {
  curJob.Process()
});

這是等待多個並行操作的兩種模式。 訣竅是您還必須將主線程也視為並行操作之一。 否則,在工作線程中的完成信號與等待來自主線程的信號之間存在微妙的競爭狀態。

int threadCount = 1;
ManualResetEvent finished = new ManualResetEvent(false);
for (int i = 0; i < NUM_WORK_ITEMS; i++)
{
  Interlocked.Increment(ref threadCount); 
  ThreadPool.QueueUserWorkItem(delegate 
  { 
      try 
      { 
           // do work 
      } 
      finally 
      { 
          if (Interlocked.Decrement(ref threadCount) == 0) finished.Set();
      } 
  }); 
}
if (Interlocked.Decrement(ref threadCount) == 0) finished.Set();
finished.WaitOne(); 

作為個人喜好,我喜歡使用CountdownEvent類為我做計數。NET4.0中提供了該類。

var finished = new CountdownEvent(1);
for (int i = 0; i < NUM_WORK_ITEMS; i++)
{
  finished.AddCount();
  ThreadPool.QueueUserWorkItem(delegate 
  { 
      try 
      { 
           // do work 
      } 
      finally 
      { 
        finished.Signal();
      } 
  }); 
}
finished.Signal();
finished.Wait(); 

上面的示例使用ThreadPool ,但是您可以將其交換為您喜歡的任何線程機制。

TPL的另一種可能性是,假設jobs是要處理的項目的集合或要運行的子線程:

Task.WaitAll(jobs
    .Select(job => TaskFactory.StartNew(() => /*run job*/))
    .ToArray());

只是為了好玩,@ Reed使用Monitor完成了什么。 :P

class Program
{
    static void Main(string[] args)
    {
        int numThreads = 10;
        int toProcess = numThreads;
        object syncRoot = new object();

        // Start workers.
        for (int i = 0; i < numThreads; i++)
        {
            new Thread(delegate()
            {
                Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
                // If we're the last thread, signal
                if (Interlocked.Decrement(ref toProcess) == 0)
                {
                    lock (syncRoot)
                    {
                        Monitor.Pulse(syncRoot);
                    }
                }
            }).Start();
        }

        // Wait for workers.
        lock (syncRoot)
        {
            if (toProcess > 0)
            {
                Monitor.Wait(syncRoot);
            }
        }

        Console.WriteLine("Finished.");
    }
}

假設數據庫讀取器線程在完成后立即返回,則可以簡單地從啟動線程依次對所有十個線程調用Thread.Join。

張貼文章以幫助其他人,花費了很多時間來尋找像我想出的解決方案。 所以我采取了一些不同的方法。 當線程啟動和停止時,我正在拆分多個線程,並增加了一個計數器,並減少了一個計數器。 然后,在主要方法中,我想暫停並等待線程完成,所以我做了。

while (threadCounter > 0)
{
    Thread.Sleep(500); //Make it pause for half second so that we don’t spin the cpu out of control.
}

記錄在我的博客上。 http://www.adamthings.com/post/2012/07/11/ensure-threads-have-finished-before-method-continues-in-c/

如果您使用的是.NET 3.5或更低版本,則可以使用AsyncResult或BackgroundWorker的數組並計算返回的線程數(只是不要忘記通過互鎖操作來減少計數器)(請參見http://www.albahari.com / threading /作為參考)。

如果使用的是.NET 4.0,則並行是最簡單的方法。

我喜歡使用的一種更簡單的方法:

    private int ThreadsCount = 100; //initialize threads count
    private void button1_Click(object sender, EventArgs e)
    {   
        for (int i = 0; i < ThreadsCount; i++)
        {
            Thread t = new Thread(new ThreadStart(myMethod));
            t.IsBackground = true;
            t.Start(); 
        } 
    }

    private void myMethod()
    {
        //everytime a thread finishes executing decrease the threads count
        ThreadsCount = ThreadsCount - 1;

        if (ThreadsCount < 1)
        {
            //if all threads finished executing do whatever you wanna do here..
            MessageBox.Show("Finished Executing all threads!!!");
        }
    }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM