为工作生成多个线程,然后等到所有完成

时间:2010-03-27 10:33:19

标签: c# multithreading backgroundworker

只想就多线程任务的“最佳实践”提出一些建议。

作为一个例子,我们有一个C#应用程序,它在启动时从我们数据库中的各种“类型”表中读取数据,并将信息存储在我们传递给应用程序的集合中。这可以防止我们每次需要这些信息时都会访问数据库。

此时应用程序正在同步读取10个表中的数据。我真的希望从一个并行运行的不同线程中的每个表中读取应用程序。在继续启动应用程序之前,应用程序将等待所有线程完成。

我已经研究过BackGroundWorker,但只是想要完成上述建议。

  1. 该方法是否合乎逻辑,以加快应用程序的启动时间
  2. 我们如何才能最好地处理所有线程,记住每个线程的工作是彼此独立的,我们只需要等待所有线程完成后再继续。
  3. 我期待一些答案

12 个答案:

答案 0 :(得分:92)

我对此的偏好是通过单个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.");
    }
}

这很好用,可以扩展到任意数量的线程处理,而不会引入锁定。

答案 1 :(得分:28)

我喜欢@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.");
    }
}

答案 2 :(得分:9)

如果你有一个超过64个STA线程的等待句柄,马克说。你可以用你的线程创建一个列表,并等待所有人在第二个循环中完成。

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

}  

答案 3 :(得分:7)

如果您不在.NET 4.0上,则可以使用List&lt; ManualResetEvent&gt;,每个线程一个,WaitSet。要等待多个线程,您可以考虑使用WaitAll,但要注意64个等待句柄的限制。如果你需要更多,你可以循环遍历它们并单独等待每一个。

如果您希望更快的启动时间,您可能不需要等待在启动期间读取所有数据。只需显示GUI,任何缺少的信息都可以通过某种“正在更新...”图标或类似方式显示为灰色。当信息进入时,只需触发一个事件来更新GUI。即使在读入所有表中的所有数据之前,用户也可以开始执行许多操作。

答案 4 :(得分:6)

如果您有冒险精神,可以使用C#4.0和任务并行库:

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

答案 5 :(得分:3)

以下是两种等待多个并行操作的模式。诀窍在于你必须将主线程视为并行操作之一。否则,在工作线程中完成信号和等待来自主线程的信号之间存在微妙的竞争条件。

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类为我做计数,这在.NET 4.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,但您可以将其替换为您喜欢的任何线程机制。

答案 6 :(得分:2)

只是为了好玩,@ 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.");
    }
}

答案 7 :(得分:2)

TPL的另一种可能性,假设jobs是要处理的项目集合或要运行的子线程:

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

答案 8 :(得分:1)

假设数据库读取器线程在完成后立即返回,您只需从发起线程依次调用所有十个线程上的Thread.Join。

答案 9 :(得分:0)

如果您使用的是.NET 3.5或更低版本,则可以使用AsyncResult或BackgroundWorker数组并计算已返回的线程数(只是不要忘记使用互锁操作减少计数器)(请参阅http://www.albahari.com/threading/ as参考)。

如果您使用的是.NET 4.0,则最简单的方法就是并行。

答案 10 :(得分: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!!!");
        }
    }

答案 11 :(得分:0)

发布可能会帮助其他人,花费相当多的时间寻找像我想出的解决方案。所以我采取了一种不同的方法。我正在旋转多个线程并递增计数器并在线程启动和停止时递减计数器。然后在main方法中,我想要暂停并等待线程完成,我做了。

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/