tags:

views:

62

answers:

2

Hi,

I have invoked BeginInvoke on 10 delegates in a loop. Instead of using 10 threads, the threadpool uses only two/three threads for executing the delegates. Can somebody please explain the reason for this?. The delegate execution takes only a few ms (less than 10ms).

When I logged threadpool parameters before invoking BeginInvoke it indicated that Min Threads = 2, Max Threads = 500, Available threads = 498.


I got the problem when I invoked the following managed c++ code.

void EventHelper::FireAndForget(Delegate^ d, ... array<Object^>^ args)
            {
                try
                {
                    if (d != nullptr)
                    {                                           
                        array<Delegate^>^ delegates = d->GetInvocationList();   

                        String^ message1 = String::Format("No of items in the event {0}",delegates.Length);
                        Log(LogMessageType::Information,"EventHelper.FireAndForget", message1);

                        // Iterating through the list of delegate methods.
                        for each(Delegate^ delegateMethod in delegates)
                        {
                            try
                            {
                                int minworkerThreads,maxworkerThreads,availworkerThreads, completionPortThreads;
                                ThreadPool::GetMinThreads(minworkerThreads, completionPortThreads);
                                ThreadPool::GetMaxThreads(maxworkerThreads, completionPortThreads);
                                ThreadPool::GetAvailableThreads(availworkerThreads, completionPortThreads);

                                String^ message = String::Format("FireAndForget Method {0}#{1} MinThreads - {2}, MaxThreads - {3} AvailableThreads - {4}",
                                                delegateMethod->Method->DeclaringType, delegateMethod->Method->Name, minworkerThreads, maxworkerThreads, availworkerThreads);

                                Log(LogMessageType::Information,"EventHelper.FireAndForget", message);

                                DynamicInvokeAsyncProc^ evtDelegate = gcnew DynamicInvokeAsyncProc(this, &EventHelper::OnTriggerEvent);
                                evtDelegate->BeginInvoke(delegateMethod, args, _dynamicAsyncResult, nullptr); //FIX_DEC_09 Handle Leak    
                            }
                            catch (Exception^ ex)
                            {
                                String^ message = String::Format("{0} : DynamicInvokeAsync of '{1}.{2}' failed", _id,
                                                                    delegateMethod->Method->DeclaringType, d->Method->Name);

                                Log(LogMessageType::Information,"EventHelper.FireAndForget", message);                              
                            }
                        }
                    }
                    else
                    {                   
                    }
                }
                catch (Exception^ e)
                {
                    Log(LogMessageType::Error, "EventHelper.FireAndForget", e->ToString());
                }

            }

This is the method given in the delegate

void EventHelper::OnTriggerEvent(Delegate^ delegateMethod, array<Object^>^ args)
            {
                try
                {
                    int minworkerThreads,maxworkerThreads,availworkerThreads, completionPortThreads;
                    ThreadPool::GetMinThreads(minworkerThreads, completionPortThreads);
                    ThreadPool::GetMaxThreads(maxworkerThreads, completionPortThreads);
                    ThreadPool::GetAvailableThreads(availworkerThreads, completionPortThreads);

                    String^ message = String::Format("OnTriggerEvent Method {0}#{1} MinThreads - {2}, MaxThreads - {3} AvailableThreads - {4}",
                                    delegateMethod->Method->DeclaringType, delegateMethod->Method->Name, minworkerThreads, maxworkerThreads, availworkerThreads);
                    Log(LogMessageType::Information,"EventHelper::OnTriggerEvent", message);

                    message = String::Format("Before Invoke Method {0}#{1}",
                                                delegateMethod->Method->DeclaringType, delegateMethod->Method->Name);
                    Log(LogMessageType::Information,"EventHelper::OnTriggerEvent", message);

                    // Dynamically invokes (late-bound) the method represented by the current delegate. 
                    delegateMethod->DynamicInvoke(args);
                    message = String::Format("After Invoke Method {0}#{1}",
                                                delegateMethod->Method->DeclaringType, delegateMethod->Method->Name);
                    Log(LogMessageType::Information,"EventHelper::OnTriggerEvent", message);
                }
                catch (Exception^ ex)
                {
                    Log(LogMessageType::Error, "EventHelper.OnTriggerEvent", ex->ToString());
                }
            }
+4  A: 

The threadpool deliberately waits a little while before starting up new threads - if the delegates execute quickly anyway (which it sounds like they do), it's more efficient to execute them on a few threads than to spin up new ones.

From the docs for ThreadPool:

When all thread pool threads have been assigned to tasks, the thread pool does not immediately begin creating new idle threads. To avoid unnecessarily allocating stack space for threads, it creates new idle threads at intervals. The interval is currently half a second, although it could change in future versions of the .NET Framework..NET Framework.

Jon Skeet
+5  A: 

You wouldn't want 10 threads to be created for this. The optimal situation is to have as many active threads as you have cores. You'll find that ThreadPool.MinThreads equals the # of logical CPU's on your PC.

Additional Threads will be created but the ThreadPool delays that on purpose. The algorithm in Fx4 has been improved, see this page. A quick look at the picture at the bottom will help you understand the principle.

Extra threads are only helpful to compensate for blocked threads, but this is difficult to get exactly right.

Henk Holterman