53

just want some advice on "best practice" regarding multi-threading tasks.

as an example, we have a C# application that upon startup reads data from various "type" table in our database and stores the information in a collection which we pass around the application. this prevents us from hitting the database each time this information is required.

at the moment the application is reading data from 10 tables synchronously. i would really like to have the application read from each table in a different thread all running in parallel. the application would wait for all the threads to complete before continuing with the startup of the application.

i have looked into BackGroundWorker but just want some advice on accomplishing the above.

  1. Does the method sound logical in order to speed up the startup time of our application
  2. How can we best handle all the threads keeping in mind that each thread's work is independent of one another, we just need to wait for all the threads to complete before continuing.

i look forward to some answers

Erik Philips
  • 53,428
  • 11
  • 128
  • 150
pharoc
  • 531
  • 1
  • 5
  • 3
  • 1
    Take a look at the parrallel task library, this will really simplify things for you. – TimothyP Mar 27 '10 at 10:34
  • are you referring to Task Parallel Library (TPL)? I shall investigate it – pharoc Mar 27 '10 at 10:37
  • 1
    TPL is fantastic for this, but requires at least .NET 3.5sp1 (if you install Rx) or .NET 4 RC (built in framework) to use.... You've suggested you need a .NET 2 solution. – Reed Copsey Apr 02 '10 at 22:33
  • 3
    Just out of curiosity, have you tried any of these solutions and found a performance benefit? If the underlying data you're reading is coming from the same source, there may not be any benefit at all to making the reads parallel, since they will end up waiting on each-other for resource access anyway. All that happens is additional context-switching overhead. Multi-threading is really only beneficial for performance if a) you actually have multiple cores and b) you're gated by computation and not access to a shared resource – Dan Bryant Apr 03 '10 at 00:51
  • 1
    It would be really really nice if this question had an accepted answer, especially with the number of different users who contributed their time to this one – demongolem Jul 13 '12 at 19:28

12 Answers12

91

My preference for this is to handle this via a single WaitHandle, and use Interlocked to avoid locking on a counter:

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

This works well, and scales to any number of threads processing, without introducing locking.

Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
  • 1
    You are so very right. All the other solutions would likely spuriously wake up the waiting thread or at least place it on the ready queue. Sometimes this must be avoided - and if not, at least it *should* be avoided. Me shall delete. :P – Andras Vass Apr 02 '10 at 22:50
  • @andras: (btw - with the "locking at the end of processing" comment on the other answer - even locking there can create a "queue"ing effect that delays things. With 10 threads, this isn't a problem, but if you're trying to synchronize against hundreds, or if you're rescheduling as you go, it can actually slow things down) – Reed Copsey Apr 02 '10 at 23:06
  • @Reed: if we are really nitpicking, :) it can be argued that `Interlocked` will create a "queue"ing effect as well. (No two CPU-s can hold the contents of the same address in their cache exclusively for write at the same time. They must invalidate the cache line and pass the new content around through either main memory or inter-cache communication. This creates a "queue"ing effect if you update the same memory location from different CPUs - the writes must be serialized.) – Andras Vass Apr 02 '10 at 23:18
  • @andras: It's more that they'll invalidate, and do a refresh of the cache line - but it doesn't actually lock, and create a queue. A lock invalidates the cache line, but also forces the other threads to wait (which, arguably, is worse than the cache line miss...) – Reed Copsey Apr 02 '10 at 23:55
  • @Reed: In the end, I think you cannot put two different people at the same spot at the same time. Interlocked above on one thread will force the same interlocked operation on other threads to wait... (it does not matter whether they are served in FIFO, LIFO, or in some other arbitrary order, they have to wait. You might as well say they have to wait in a queue - which is of course not precise, but can be visualized easily. And is certainly a kind of "queueing" effect, if by that you mean that it results in processing the memory stores sequentially, one-by-one..) :P – Andras Vass Apr 03 '10 at 00:20
  • @Reed: of course, it will be faster than `lock`. In fact, it is as close to locking as one can get. It is provided by the bare metal in the form of `lock` prefixed assembly instructions. These will naturally always be faster than a higher-level lock - just as you have said. – Andras Vass Apr 03 '10 at 00:23
  • @Reed: (...and while we are "nitpicking" here: congrats to the award you have recently received. :) – Andras Vass Apr 03 '10 at 00:47
  • This solution doesn't work I believe? At least for me. The last thread signals the event on creation I believe. Whats the solution that I want the main thread to wait till the last thread FINISHES and then signals. However I struggled to pass a particular function to define the thread's entry point. So if someone wants to define that function, you can add it after the "IF statement" in the delegate scope. – Shoaib May 03 '16 at 06:25
  • @ShoaibHaider Just put your logic before the set call - it signals when you call Set() – Reed Copsey May 03 '16 at 18:19
  • Oh. I tried putting the desired thread function before the if condition and it didn't call it. If it was after the if block then the function executed. That would have been the problem. But is it possible that the function won't execute if I put it before the condition like I felt? – Shoaib May 13 '16 at 17:15
  • It doesn't make any difference in a toy example, but in general, it's probably a good idea to dispose of `resetEvent` -- `using (var resetEvent = new ManualResetEvent(false)) { ... }`? – Myk Jun 02 '18 at 11:06
28

I like @Reed's solution. Another way to accomplish the same in .NET 4.0 would be to use a 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.");
    }
}
Jack Leitch
  • 708
  • 6
  • 5
  • This is good, only problem is you must know how many threads there are going to be before going into your loop... so if you have logic with `continue`s in your loop, this is not as helpful, but if not needed, this feels like the cleaner way. – Serj Sagan Mar 03 '18 at 01:27
  • It doesn't make any difference in a toy example, but in general, it's probably a good idea to dispose of `countdownEvent` -- `using (var countdownEvent = new CountdownEvent(numThreads)) { ... }`? – Myk Jun 02 '18 at 11:04
9

If you have more than 64 wait handles for an STA Thread as Mark says. you could create a list with your threads and wait for all to complete in a second loop.

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

}  
Benjamin Ortuzar
  • 7,801
  • 6
  • 41
  • 46
7

If you're not on .NET 4.0 then you can use a List<ManualResetEvent>, one for each thread and Wait for them to be Set. To wait on multiple threads you could consider using WaitAll but watch out for the limit of 64 wait handles. If you need more than this, you can just loop over them and wait for each one individually.

If you want a faster startup exprience, you probably don't need to wait for all the data to be read during startup. Just display the GUI and any information that is missing can be shown greyed out with some sort of "Updating..." icon or similar. When the information comes in, just fire an event to update the GUI. There could be many operations that the user can begin to perform even before all the data from all tables is read in.

Mark Byers
  • 811,555
  • 193
  • 1,581
  • 1,452
6

If you're feeling adventurous you can use C# 4.0 and the Task Parallel Library:

Parallel.ForEach(jobList, curJob => {
  curJob.Process()
});
Benjamin Ortuzar
  • 7,801
  • 6
  • 41
  • 46
  • mmmmm thx but C# 4.0 seems to far at the right end of the curve for business apps. might have to come up with a C# 2.0 solution. – pharoc Mar 27 '10 at 10:44
4

Here are two patterns for waiting on multiple parallel operations. The trick is that you have to treat your main thread as if it were one of the parallel operations as well. Otherwise, there is a subtle race condition between the signalling of completion in the worker threads and the waiting on that signal from the main thread.

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

As a personal preference I like using the CountdownEvent class to do the counting for me which is available in .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(); 

The examples above use the ThreadPool, but you can swap that for whatever threading mechanism you prefer.

Brian Gideon
  • 47,849
  • 13
  • 107
  • 150
2

Another possibility with TPL, assuming jobs is the collections of items to process, or subthreads to run:

Task.WaitAll(jobs
    .Select(job => TaskFactory.StartNew(() => /*run job*/))
    .ToArray());
BartoszKP
  • 34,786
  • 15
  • 102
  • 130
2

Just for fun, what @Reed has done, with 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.");
    }
}
Andras Vass
  • 11,478
  • 1
  • 37
  • 49
  • @andras: I put in another modification of this using a single ManualResetEvent - it is nicer because it completely eliminates the need for the locking in the processing, so it scales much better to larger worker counts. The semaphore option is similar, although I personally think more complicated than my answer below. – Reed Copsey Apr 02 '10 at 22:15
  • @Reed: I was quite unhappy with the semaphore solution. (I have even deleted it from the answer at one point.) Even the Monitor based solution seemed so much nicer. Your solution is better in all respects. (...and performance wise, it's king....) :) – Andras Vass Apr 02 '10 at 22:23
1

Assuming the database reader threads return as soon as they're done, you can simply call Thread.Join on all ten threads in turn from the initiating thread.

Marcelo Cantos
  • 181,030
  • 38
  • 327
  • 365
0

Posting to maybe help some others, spent quite a bit of time looking for a solution like what I came up with. So I took a little different approach. I was spinning off numerous threads and incremented a counter and decremented a counter as a thread started and stopped. Then in the main method I was wanting to pause and wait for threads to complete I did.

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

Documented on my blog. http://www.adamthings.com/post/2012/07/11/ensure-threads-have-finished-before-method-continues-in-c/

Adam
  • 3,615
  • 6
  • 32
  • 51
  • I couldn't get Thread.Join() to stop PageLoad from finishing, where as making a simple counter, I could loop through until all threads finished. – Adam Jul 11 '12 at 19:10
0

If you are using .NET 3.5 or below, you can use an array of AsyncResult or BackgroundWorker and count how many threads have returned (just don't forget to decrease counter with interlocked operations) (see http://www.albahari.com/threading/ as a reference).

If you are using .NET 4.0 a parallel for is the simplest approach.

Danny Varod
  • 17,324
  • 5
  • 69
  • 111
  • @Kiquenet Nothing ready that I currently have access to. I am not sure that this is the right site to ask for full code examples. – Danny Varod Jan 01 '14 at 11:27
0

an easier method I like to use:

    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!!!");
        }
    }
Desolator
  • 22,411
  • 20
  • 73
  • 96