web-dev-qa-db-fra.com

Parallel.ForEach se bloque-t-il?

La fonction .net Parallel.ForEach bloque-t-elle le thread appelant? Ma conjecture quant au comportement est l'une d'entre elles:

  1. Oui, il se bloque jusqu'à ce que l'exécution de l'élément le plus lent revienne.
  2. Non, il ne bloque pas et rend le contrôle immédiatement. Les éléments à exécuter en parallèle se font sur des threads d'arrière-plan.

Ou peut-être que quelque chose d'autre se passe, quelqu'un le sait avec certitude?

Cette question s'est posée lors de l'implémentation de ceci dans une classe de journalisation:

public class MultipleLoggingService : LoggingServiceBase
{
    private readonly List<LoggingServiceBase> loggingServices;

    public MultipleLoggingService(List<LoggingServiceBase> loggingServices)
    {
        this.loggingServices = loggingServices;
        LogLevelChanged += OnLogLevelChanged;
    }

    private void OnLogLevelChanged(object sender, LogLevelChangedArgs args)
    {
        loggingServices.ForEach(l => l.LogLevel = LogLevel);
    }

    public override LogMessageResponse LogMessage(LogMessageRequest request)
    {
        if (request.LogMessage)
            Parallel.ForEach(loggingServices, l => l.LogMessage(request));

        return new LogMessageResponse{MessageLogged = request.LogMessage};
    }
}

Notez que la méthode LogMessage appelle d'autres services de journalisation. J'ai besoin que cette partie revienne immédiatement, donc elle ne bloque pas le thread appelant.


Mise à jour: Basé sur les commentaires des autres (nous avons confirmé que le comportement est n ° 1). J'ai donc pris conseil d'utiliser la bibliothèque Task et réécrit la boucle comme ceci:

          if (request.LogMessage)
            foreach (var loggingService in loggingServices)
                Task.Factory.StartNew(() => loggingService.LogMessage(request));
49
Paul Fryer

Le numéro 1 est correct; Parallel.ForEach ne revient que lorsque la boucle est terminée. Si vous ne souhaitez pas ce comportement, vous pouvez simplement exécuter votre boucle en tant que Task et l'exécuter sur un autre thread.

57
Gabe

Re votre mise à jour, StartNew dans un foreach normal ():

Ce n'est peut-être pas le plus optimal pour les grandes collections, et vous n'obtenez aucun point pour gérer les erreurs.

Vos services de journalisation ne contiennent probablement pas des milliers d'éléments, mais la gestion des erreurs reste un point.

Considérer:

Task.Factory.StartNew(() => 
{
   try
   {
        Parallel.ForEach(loggingServices, l => l.LogMessage(request));
   }
   catch(SomeException ex)
   {
       // at least try to log it ...
   }
});
10
Henk Holterman