Parallel.ForEach() Vs For ( Vs ForEach): Loop a Confronto

Parallel.ForEach: Usarlo o non usarlo? Questa è la domanda che mi pongo spesso quando programma, ma la risposta è davvero molto semplice ed è la seguente: porta veramente dei vantaggi e serve veramente per il codice che stai scrivendo?

Parallel.ForEach (in breve) e confronto con For e ForEach

Quali sono le differenze di base?

  • For / ForEach: il ciclo lavora su tutti gli elementi in maniera SEQUENZIALE

  • Parallel.ForEach: gli elementi vengono utilizzati tutti in maniera PARALLELA

Nella modalità Parallel non vi è ordine preventivo di come verranno utilizzati gli elementi.

Parallel.ForEach: Codice per tutti

Ora -per l’articolo che ho in mente- ho già scritto troppo. Passiamo al codice?

Importante: ricordatevi di aggiungere le seguenti using

  • using System.Linq;

  • using System.Threading.Tasks;

per potere utilizzare l’esempio proposto. Parallel.ForEach

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61

public void DoSampleParallelForEach()
        {

            bool doDelay = true;
            int delayMs = 1;

            for (int idxStep = 0; idxStep < 1000; idxStep++)
            {

                int totalItems = (idxStep + 1) * 100;

                int[] items = Enumerable
                               .Range(1, totalItems)
                               .ToArray();

                DateTime now = DateTime.Now;
                Parallel.ForEach(items, (i) =>
                {
                    bool doSomething = false;
                    if (doDelay)
                    {
                        Task.Delay(delayMs);
                    }
                }
                );
                double msParallel = DateTime.Now.Subtract(now).TotalMilliseconds;


                now = DateTime.Now;
                foreach (int i in items)
                {
                    bool doSomething = false; 
                    if (doDelay)
                    {
                        Task.Delay(delayMs);
                    }
                }
                double msForEach = DateTime.Now.Subtract(now).TotalMilliseconds;

                now = DateTime.Now;
                for (int i = 0; i < items.Length; i++)
                {
                    bool doSomething = false;
                    if (doDelay)
                    {
                        Task.Delay(delayMs);
                    }
                }
                double msFor = DateTime.Now.Subtract(now).TotalMilliseconds;

                if (msParallel < msForEach || msParallel < msFor)
                {
                    Console.WriteLine($"Total Items: {totalItems} | Parallel: {msParallel} | ForEach: {msForEach} | For: {msFor}");
                }
            }

            Console.WriteLine("***");

        }

Parallel.ForEach(): Quando conviene?

L’esempio precedente non esegue molto codice, se non creare l’array items aumentandone sempre la dimensione per poi lanciare poco (o nulla) coi loop esposti. Per rendere meglio l’idea ho lasciato la variabile doDelay a true e questo genera un ritardo impercettibile di 1ms. Per quantificare il tutto in numeri ho inserito anche tre cronometri ed alla fine verrà mostrato a video il valore quando Parallel.ForEach impiegherà meno degli altri casi.

Ora provo a rispondere alla domanda iniziale: quando conviene?

  • Quando all’interno ci sono chiamate a metodi await/async (o Delay come inserito nell’esempio)

  • Quando il corpo del ciclo deve eseguire operazioni pesanti 

Della Parallel.ForEach ho fatto usi ed abusi quando ho scritto un tool per un cliente che aveva lo scopo di processare un numero di file fotografici elevato e (sempre nello stesso tool) scrivere per ogni immagine un file della stessa immagine in miniatura.

! Parallel.ForEach() Vs For ( Vs ForEach): Loop a Confronto (Test)

Come vedete il risparmi di tempo (in questo caso espressi in millisecondi) è notevole solo aggiungendo un delay di 1ms. Provate a pensare su operazioni async/await oppure elaborazioni pesanti.

A mio avviso non sempre ha senso rimettere mano al vecchio codice per migrarlo alla modalità Parallel. Primo tra tutti i motivi è la regola “se funziona NON toccarlo, che è meglio” e questo vale per il bene di tutti.

Ora non vi resta che divertivi.