views:

1130

answers:

8

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

+7  A: 

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
ManualResetEvent is my favorite synchronization tool!
Polaris878
Mark: This can be done with a single ManualResetEvent and the Interlocked class. See my response below for details.
Reed Copsey
+3  A: 

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

Parallel.ForEach(jobList, curJob => {
  curJob.Process()
});
Benjamin Ortuzar
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
+1  A: 

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
+2  A: 

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
+1 Sometimes a simple `Join` works great.
Ron Klein
+1  A: 

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
@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
@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
@Reed: Corrected. Thanks. :)
andras
+12  A: 

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
:-) Great solution! +1
andras
very elegant. Me likes. +1
Joel
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
@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
@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
@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
@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
@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
@Reed: (...and while we are "nitpicking" here: congrats to the award you have recently received. :)
andras
@andras: Thanks!
Reed Copsey
A: 

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
+2  A: 

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