3 votes

Doute sur le retard d'exécution du pool de threads

J'ai invoqué BeginInvoke sur 10 délégués dans une boucle. Au lieu d'utiliser 10 threads, le threadpool n'utilise que deux/trois threads pour exécuter les délégués. Est-ce que quelqu'un peut expliquer la raison de cela? L'exécution du délégué prend seulement quelques ms (moins de 10ms).

Quand j'ai enregistré les paramètres du threadpool avant d'invoquer BeginInvoke, cela indiquait que Min Threads = 2, Max Threads = 500, Threads disponibles = 498.


J'ai rencontré le problème lorsque j'ai invoqué le code C++ géré suivant.

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

            String^ message1 = String::Format("Nombre d'éléments dans l'événement {0}",delegates.Length);
            Log(LogMessageType::Information,"EventHelper.FireAndForget", message1);

            // Parcours de la liste des méthodes de délégué.
            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("Méthode FireAndForget {0}#{1} MinThreads - {2}, MaxThreads - {3} Threads disponibles - {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} : L'appel asynchrone dynamique de '{1}.{2}' a échoué", _id,
                                                        delegateMethod->Method->DeclaringType, d->Method->Name);

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

}

Ceci est la méthode donnée dans le délégué

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

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

        message = String::Format("Avant d'invoquer la méthode {0}#{1}",
                                    delegateMethod->Method->DeclaringType, delegateMethod->Method->Name);
        Log(LogMessageType::Information,"EventHelper::OnTriggerEvent", message);

        // Invoque dynamiquement (liaison tardive) la méthode représentée par le délégué actuel. 
        delegateMethod->DynamicInvoke(args);
        message = String::Format("Après avoir invoqué la méthode {0}#{1}",
                                    delegateMethod->Method->DeclaringType, delegateMethod->Method->Name);
        Log(LogMessageType::Information,"EventHelper::OnTriggerEvent", message);
    }
    catch (Exception^ ex)
    {
        Log(LogMessageType::Error, "EventHelper.OnTriggerEvent", ex->ToString());
    }
}

6voto

Henk Holterman Points 153608

Vous ne voudriez pas que 10 threads soient créés pour cela. La situation optimale est d'avoir autant de threads actifs que de cœurs. Vous constaterez que ThreadPool.MinThreads équivaut au nombre de CPU logiques sur votre PC.

Des threads supplémentaires seront créés mais le ThreadPool retarde cela à dessein. L'algorithme dans Fx4 a été amélioré, voir cette page. Un coup d'œil rapide à l'image en bas vous aidera à comprendre le principe.

Des threads supplémentaires sont seulement utiles pour compenser les threads bloqués, mais cela est difficile à obtenir exactement correctement.

4voto

Jon Skeet Points 692016

Le threadpool attend délibérément un peu avant de démarrer de nouveaux threads - si les délégués s'exécutent rapidement de toute façon (ce qui semble être le cas), il est plus efficace de les exécuter sur quelques threads que d'en démarrer de nouveaux.

Depuis les docs pour ThreadPool:

Lorsque tous les threads du pool de threads ont été assignés à des tâches, le pool de threads ne commence pas immédiatement à créer de nouveaux threads inactifs. Pour éviter d'allouer inutilement de l'espace de pile pour les threads, il crée de nouveaux threads inactifs à intervalles. L'intervalle est actuellement d'une demi-seconde, bien qu'il puisse changer dans les futures versions du .NET Framework.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X