2015-09-18 10 views
10

Ho scritto un programma che utilizza tutti i core disponibili utilizzando Parallel.ForEach. L'elenco per ForEach contiene ~ 1000 oggetti e il calcolo per ogni oggetto richiede un po 'di tempo (~ 10 sec). In questa configurazione scenario che un timer come questo:System.Timers.Timer gravemente inaccurato

timer = new System.Timers.Timer(); 
timer.Elapsed += TimerHandler; 
timer.Interval = 15000; 
timer.Enabled = true; 

private void TimerHandler(object source, ElapsedEventArgs e) 
{ 
     Console.WriteLine(DateTime.Now + ": Timer fired"); 
} 

Al momento il metodo TimerHandler è uno stub per assicurarsi che il problema non è causato da questo metodo.

La mia aspettativa era che il metodo TimerHandler fosse eseguito ogni ~ 15 secondi. Tuttavia, il tempo tra due chiamate a questo metodo raggiunge anche i 40 secondi, quindi 25 secondi in più. Utilizzando new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount -1 } per il metodo Parallel.ForEach, ciò non accade e viene visualizzato l'intervallo previsto di 15 secondi.

È inteso che devo assicurarmi che ci sia sempre un core disponibile per ogni timer attivo? Sembra essere un po 'strano ancora di più, perché il "riservato" potrebbe essere una risorsa preziosa per il mio calcolo.

Modifica: come indicato da Yuval, l'impostazione di un minimo fisso di thread nel pool tramite ThreadPool.SetMinThreads ha risolto il problema. Ho anche provato new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount } (quindi senza lo -1 nella domanda iniziale) per il metodo Parallel.ForEach e anche questo risolve il problema. Tuttavia, non ho una buona spiegazione del perché queste modifiche abbiano risolto il problema. Forse ci sono stati così tanti thread creati che il thread del timer è stato "perso" per un tempo "lungo" fino a quando non è stato eseguito di nuovo.

+1

Se si imposta in modo esplicito il numero di thread nel pool di thread prima di eseguire il ciclo parallelo, si verifica ancora? (Usando 'ThreadPool.SetMinThreads') –

+0

stai pegging l'utilizzo del processore quando usi tutti i processori? in tal caso, tutti gli eventi risponderanno più lentamente a causa del modo in cui Windows elabora il pump dei messaggi. – Jeremy

+1

Sospetto che il thread che esegue i thread paralleli (che probabilmente è lo stesso thread che esegue il timer) si blocchi. Se riesci a trovare un modo per eseguire il timer su un thread diverso, il tuo problema potrebbe andare via. Il thread che esegue il tuo timer potrebbe essere diverso a seconda di come è ospitata la tua app btw (ad esempio Windows form vs console app) –

risposta

1

La classe Parallel utilizza una funzione TPL interna denominata attività auto-replicanti. Sono destinati a consumare tutte le risorse di thread disponibili. Non so che tipo di limiti ci siano ma sembra che sia tutto consumato. Ho risposto fondamentalmente alla stessa domanda qualche giorno fa.

La classe Parallel è incline a generare folle quantità di attività senza alcun limite. È facile provocarlo a generare letteralmente thread illimitati (2 al secondo). Considero la classe Parallel inutilizzabile senza un max-DOP specificato manualmente. È una bomba a tempo che esplode in modo casuale sotto carico.

Parallel è particolarmente tossico in scenari ASP.NET in cui molte richieste condividono un pool di thread.

Aggiornamento: Ho dimenticato di fare il punto chiave. I tick del timer sono accodati al pool di thread. Se il pool è saturo, entrano in linea ed eseguono in un secondo momento. (Questo è il motivo per cui i tick del timer possono accadere contemporaneamente o dopo che i timer sono stati fermati.) Questo spiega cosa stai vedendo. Il modo per risolverlo è quello di correggere l'overloading del pool.

La soluzione migliore per questo particolare scenario sarebbe uno schedulatore di attività personalizzato con una quantità fissa di thread. Parallel può essere utilizzato per utilizzare tale utilità di pianificazione. Parallel Extension Extras ha un tale scheduler. Prendi il lavoro fuori dal pool di thread condiviso a livello globale. Normalmente, consiglierei PLINQ ma non è in grado di prendere uno scheduler. In un certo senso sia Parallel che PLINQ sono API inutilmente azzoppate.

Non utilizzare ThreadPool.SetMinThreads. Non scherzare con le impostazioni globali del processo. Basta lasciare il pool di thread poveri da solo.

Inoltre, non utilizzare Environment.ProcessorCount -1 perché quello spreca un core.

il timer è già eseguita nel proprio thread

Il timer è una struttura di dati nel kernel del sistema operativo. Non c'è thread fino a quando un segno di spunta deve essere messo in coda. Non sei sicuro di come funzioni esattamente, ma alla fine il segno di spunta viene accodato al pool di thread in .NET. Questo è all'avvio del problema.

Come soluzione è possibile avviare un thread che dorme in un ciclo per simulare un timer. Questo è un trucco, però, perché non risolve la causa principale: il pool di thread sovraccarico.

Problemi correlati