产卵多个线程的工作,然后等到所有完成
只是想就multithreading任务的“最佳实践”提出一些build议。
作为一个例子,我们有一个C#应用程序,它在启动时从我们数据库中的各种“types”表中读取数据,并将这些信息存储在我们传递给应用程序的集合中。 这会阻止我们在每次需要这些信息时都敲击数据库。
目前应用程序正在同步从10个表中读取数据。 我真的希望从不同的线程中的每个表中读取应用程序并行运行。 在继续启动应用程序之前,应用程序会等待所有线程完成。
我已经看了BackGroundWorker,但只是想完成上述一些build议。
- 该方法听起来是合乎逻辑的,以加快我们的应用程序的启动时间
- 我们怎样才能最好地处理所有的线程,牢记每个线程的工作是相互独立的,我们只需要等待所有的线程完成,然后继续。
我期待着一些答案
我的首选是通过一个WaitHandle来处理,并使用Interlocked来避免在计数器上locking:
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."); } }
这个效果很好,可以扩展到任意数量的线程处理,而不会引入locking。
我喜欢@里德的解决scheme。 在.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."); } }
如果你有一个STA线程超过64个等待句柄,正如Mark所说的那样。 你可以创build一个列表与你的线程,并等待所有完成在第二个循环。
//check that all threads have completed. foreach (Thread thread in threadList) { thread.Join(); }
如果你不在.NET 4.0上,那么你可以使用一个List < ManualResetEvent >,每个线程一个,并等待它们被设置 。 要等待多个线程,你可以考虑使用WaitAll,但要注意64个等待句柄的限制。 如果你需要比这更多的东西,你可以绕过它们,单独等待每一个。
如果你想要更快的启动exprience,你可能不需要等待所有的数据在启动时被读取。 只需显示GUI,任何缺失的信息都可以用某种“正在更新…”图标或类似图标显示。 当信息进入时,只需触发一个事件来更新GUI。 甚至在读入所有表中的所有数据之前,用户可以开始执行许多操作。
如果您感觉冒险,可以使用C#4.0和任务并行库:
Parallel.ForEach(jobList, curJob => { curJob.Process() });
只是为了好玩,@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."); } }
这里有两种等待多个并行操作的模式。 诀窍是,你必须把你的主线程看作是并行操作之一。 否则,在工作线程完成信号和主线程等待信号之间存在细微的竞争条件。
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计数。
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());
假设数据库读取器线程完成后立即返回,您可以简单地从启动线程调用所有十个线程上的Thread.Join。
如果您使用的是.NET 3.5或更低版本,则可以使用AsyncResult或BackgroundWorker数组,并计算返回的线程数(请不要忘记使用互锁操作减less计数器)(请参见http://www.albahari.com /线程/作为参考)。
如果您使用.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!!!"); } }
发布也许可以帮助其他人,花了相当多的时间寻找像我想出的解决scheme。 所以我采取了一些不同的方法。 我旋转了大量的线程,增加了一个计数器,并在线程启动和停止时递减一个计数器。 然后在主要的方法,我想暂停,等待线程完成我做的。
while (threadCounter > 0) { Thread.Sleep(500); //Make it pause for half second so that we don't spin the cpu out of control. }
logging在我的博客上。 http://www.adamthings.com/post/2012/07/11/ensure-threads-have-finished-before-method-continues-in-c/