Threadpool threads and waiting for large number of threads to finish

This article is about waiting for a large number of ThreadPool threads to finish executing, and two exceptions that can occur in a winforms application when trying to wait for ThreadPool threads to finish. 

Long long time ago in the days of visual studio 2008  i was playing with ThreadPool trying to queue a large number of threads and waiting for them to finish. I recreated the issues I stumbled upon in the new and shiny visual studio 2010 express.I created a new windows forms application and tried out the following code:

 

private static void CreateLotsOfThreads()

{

    int numberOfThreads = 1000; // Large number of threads 

    // An array whichis global to all the threads where each thread reports

    // to a unique index when it finishes working

    ManualResetEvent[] ResetEvents = new ManualResetEvent[numberOfThreads];

    // Initialize ManualResetEvent objects with a non signaled state, causing threads

    // which execute WaitOne() on to block until the thread has finished working 

    for (int threadIndex = 0; threadIndex <numberOfThreads; threadIndex++)

        ResetEvents[threadIndex] = new ManualResetEvent(false); 

    for (int i = 0; i < numberOfThreads; i++)

    {

        // The index to set in the ResetEvents array when the current thread finishes.

        // The copy of i prevents the anonymous methods from sharing variables with the

        // outerscope (see remarks at the end of the article for more information)

        int guaranteedIndex = i; 

        // Queue ananonymous delegate for the example

        ThreadPool.QueueUserWorkItem(

            delegate

            {

                //Thread work

                Thread.Sleep(10);

                //Thread finished working

               ResetEvents[guaranteedIndex].Set();

            });

    }

    // Wait for allof the threads to finish

    WaitHandle.WaitAll(ResetEvents);

    // At this pointall the threads finished execution

}

       

When executing the above code, I received this error message:

System.NotSupportedException wasunhandled

 Message=The number of WaitHandles must be less than or equal to 64.

 

I changed the value of numberOfThreads to 64 and executed it again, what can possibly go wrong now? Well this time a different error message appeared:

System.NotSupportedException wasunhandled

 Message=WaitAll for multiple handles on a STA thread is not supported.

 

Windows Forms applications use a Single Threaded Apartment (STA) andWaitAll() supports only Multithreaded Apartment (MTA) so it cannot be usedhere, but does not matter, even if 64 threads were running my goal was to queue a much larger amount so this could not be of help anyway (And i don’t want to execute threads in batches of 64)

 

One solution to the problem is to wait for each thread individualy:

 

private void CreateAndWaitSolution1()

{

    int numberOfThreads = 1000; // Large number of threads

    ManualResetEvent[] ResetEvents = new ManualResetEvent[numberOfThreads];

 

    for (int threadIndex = 0; threadIndex <numberOfThreads; threadIndex++)

        ResetEvents[threadIndex] = new ManualResetEvent(false); 

    for (int i = 0; i < numberOfThreads; i++)

    {

        int guaranteedIndex = i; 

        ThreadPool.QueueUserWorkItem(

            delegate

            {

                Thread.Sleep(10);

               ResetEvents[guaranteedIndex].Set();

            });

   

    // Wait until allthreads set their ManualResetEvent state to signaled

    for (int i = 0; i < 1000; i++)

        ResetEvents[i].WaitOne();

    // At this pointall the threads finished execution

}

 

The above approach is simple, just iterate over the array which is global to all the threads and wait for each thread to finish. ManualResetEvent is threadsafe, and all of the instances are created before any thread is executed, so it should be pretty safe.

In the above code only the UI thread does the waiting, but it is possible to make a specific ThreadPool thread  wait for another ThreadPool thread to finishbefore it starts working, (assuming there is a dependency between the two threds),the code above allows this by using the ResetEvents array. Example:

 

ThreadPool.QueueUserWorkItem(

delegate

{

    // Make threadnumber 6 wait thread number 7 before it begins working

    if (guaranteedIndex== 5)

        ResetEvents[6].WaitOne();

    Thread.Sleep(10);                       

    ResetEvents[guaranteedIndex].Set();

});

 The above example shows how to make a specific ThreadPool thread waitfor another ThreadPool thread to finish before it begins working.

  

Back to the main subject, A different approach for waiting for threads to finish does not require the usage of ManualResetEvent, but it’s good only if threads do not depend on one another. It involves counting the number of threads which finished and periodicallychecking how many threads finished working from the main UI thread:

private void CreateAndWaitSolution2()

{

    int numberOfFinishedThreads = 0;

    int numberOfThreads = 1000; // Large number of threads

 

    for (int i = 0; i < numberOfThreads; i++)

    {

        intguaranteedIndex = i; 

        ThreadPool.QueueUserWorkItem(

            delegate

            {

                Thread.Sleep(10);

                //Atomic Increments and store

                Interlocked.Increment(ref numberOfFinishedThreads);

            });

    }

     // Wait until all threads incremented the numberOfFinishedThreads counter

    while(numberOfFinishedThreads < numberOfThreads)

        Thread.Sleep(100);

    // At this pointall the threads finished execution

}

 The above makes the ManualResetEvent array unnecessary and I assume that it saves some memory, the number of code lines  is not reduces dramatically though. 

 The Interlocked.Increment prevents a race condition which can cause two separate threads to increment the counter to the same value. The sleep duration can be tweaked to be more aggressive or less aggresive depending onthe amount of time you expect the threads to finish.

 

I was concerned about UI freeze in windows forms application so  I did a little experiment on the above code: I changed numberOfThreads to 10000 and added a textbox and a button to the

main application form, I then  executed the following in the Form load event:

 

private void Form1_Load(objectsender, EventArgs e)

{

    textBox1.Text = "working...";

    Thread thread = newThread(new ThreadStart(delegate

        {

            //Delayed start, just to make sure the form will show etc.

            Thread.Sleep(1000);

            CreateAndWaitSolution1();

            //CreateAndWaitSolution2();

            // Safelyupdate the UI thread from this thread

            this.Invoke(new MethodInvoker(delegate

            {

                textBox1.Text = "finished!";

            }));

        }));

    thread.IsBackground = true;

    thread.Start();

}

 

The form was responsive, I could drag it anywhere without lag and there was no problem pressing the button. CreateAndWaitSolution1 Finished much faster than CreateAndWaitSolution2 when both ran 10000 threads, the opposite of what i expected.  I guess Interlocked.Increment is responsible for that, it blocks threads when they try to  increment the counter value at the same time which makes the total execution time longer.

   

Some more information about Threapool: 

-  Threapool  is Good for executing many short-livedthreads, providing your application with a  pool of worker threads  that are managed by the system.

- ThreadPool threads are background threads and will not keep anapplication running after all foreground threads have exited.

- You cannot join on a threadpool thread

- You cannot assign priority to ThreadPool thread

- You cannot abort a specific ThreadPool thread

 

Remarks: 

 guaranteedIndex: Because the ThreadPoolexecutes threads “when a thread pool thread becomes available” The code insidethe anonymous delegate is executed only after the loop completes, giving eachthread the last value of i and not the value that i had when the delegate codewas put into the Threadpool queue.

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Posted by: amirtal
Posted on: 5/11/2010 at 11:06 PM
Categories: .NET | Winforms
Actions: E-mail | Kick it! | DZone it! | del.icio.us
Post Information: Permalink | Comments (0) | Post RSSRSS comment feed