2012-04-14 7 views
39

La funzione .net Parallel.ForEach blocca il thread chiamante? La mia ipotesi sul comportamento è uno di questi:Parallel.ForEach Block?

  1. Sì, blocca fino a quando l'elemento più lento in esecuzione restituisce.
  2. No, non blocca e restituisce il controllo immediatamente. Gli articoli da eseguire in parallelo sono fatti su thread in background.

O forse sta succedendo qualcos'altro, qualcuno lo sa per certo?

Questa domanda è venuto quando l'applicazione del presente in una classe di registrazione:

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}; 
    } 
} 

Avviso il metodo LogMessage chiama alcuni altri servizi di registrazione. Ho bisogno che quella parte ritorni immediatamente, quindi non blocca il thread chiamante.


Aggiornamento: in base ai commenti degli altri (abbiamo confermato il comportamento è # 1). Così ho seguito il consiglio di utilizzare la libreria Task e riscritto il ciclo come questo:

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

risposta

48

numero 1 è corretta; Parallel.ForEach non viene restituito fino al completamento del ciclo. Se non vuoi questo comportamento, puoi semplicemente eseguire il tuo loop come Task ed eseguirlo su un altro thread.

+0

Grazie per la punta! Ti capita di avere un piccolo frammento di codice su come usare "Task" in questo contesto? Grazie, Paul –

+1

@PaulFryer: 'Task t = Task.TaskFactory.StartNew (() => {/ * Parallel.Per andare qui * /});' – Richard

+6

Tecnicamente, il thread chiamante viene utilizzato nel ciclo parallelo. Quindi non è "sprecato" solo bloccando il ciclo - è usato come uno dei thread di lavoro. –

8

Re l'aggiornamento, StartNew in un foreach normale():

Questo non può essere il più ottimale per grandi collezioni, e non si ottiene un punto per gestire gli errori.

I tuoi servizi di registrazione probabilmente non contengono migliaia di elementi ma il gestore degli errori rimane un punto.

considerare:

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

Ah, buon punto. Dovrò pensare a questo per un po ', se il servizio di registrazione fallisce, come registrare l'errore :-) –