ParallelLoopState: Effettuare il BREAK di un Parallel.For

Utilizzare un Parallel.For può tornare comodo per ottimizzare i tempi di esecuzione del codice. Una volta che la condizione desiderata si è verificata, ha senso continuarlo? Ovviamente la risposta è no. Per chi non ha dimestichezza coi Parallel.For oppure Parallel.Foreach vi consiglio di leggere il mio precedente articolo su come utilizzarli: Parallel.ForEach() Vs For ( Vs ForEach): Loop a Confronto

ParallelLoopState

Come potrete immaginare il classico “break;” non funziona inserito in un contesto parallelo. Al suo posto dobbiamo utilizzare ParallelLoopState (namespace System.Threading.Tasks) durante la definizione del ciclo. ParallelLoopState (System.Threading.Task)

 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
62
63
64
65
66
67
68
69
70
71

namespace System.Threading.Tasks
{
    //
    // Summary:
    //     Enables iterations of parallel loops to interact with other iterations. An instance
    //     of this class is provided by the System.Threading.Tasks.Parallel class to each
    //     loop; you can not create instances in your code.
    public class ParallelLoopState
    {
        //
        // Summary:
        //     Gets whether any iteration of the loop has thrown an exception that went unhandled
        //     by that iteration.
        //
        // Returns:
        //     true if an unhandled exception was thrown; otherwise, false.
        public bool IsExceptional { get; }
        //
        // Summary:
        //     Gets whether any iteration of the loop has called the System.Threading.Tasks.ParallelLoopState.Stop
        //     method.
        //
        // Returns:
        //     true if any iteration has stopped the loop by calling the System.Threading.Tasks.ParallelLoopState.Stop
        //     method; otherwise, false.
        public bool IsStopped { get; }
        //
        // Summary:
        //     Gets the lowest iteration of the loop from which System.Threading.Tasks.ParallelLoopState.Break
        //     was called.
        //
        // Returns:
        //     The lowest iteration from which System.Threading.Tasks.ParallelLoopState.Break
        //     was called. In the case of a System.Threading.Tasks.Parallel.ForEach``1(System.Collections.Concurrent.Partitioner{``0},System.Action{``0})
        //     loop, the value is based on an internally-generated index.
        public long? LowestBreakIteration { get; }
        //
        // Summary:
        //     Gets whether the current iteration of the loop should exit based on requests
        //     made by this or other iterations.
        //
        // Returns:
        //     true if the current iteration should exit; otherwise, false.
        public bool ShouldExitCurrentIteration { get; }

        //
        // Summary:
        //     Communicates that the System.Threading.Tasks.Parallel loop should cease execution
        //     of iterations beyond the current iteration at the system's earliest convenience.
        //
        // Exceptions:
        //   T:System.InvalidOperationException:
        //     The System.Threading.Tasks.ParallelLoopState.Stop method was previously called.
        //     System.Threading.Tasks.ParallelLoopState.Break and System.Threading.Tasks.ParallelLoopState.Stop
        //     may not be used in combination by iterations of the same loop.
        public void Break();
        //
        // Summary:
        //     Communicates that the System.Threading.Tasks.Parallel loop should cease execution
        //     at the system's earliest convenience.
        //
        // Exceptions:
        //   T:System.InvalidOperationException:
        //     The System.Threading.Tasks.ParallelLoopState.Break method was called previously.
        //     System.Threading.Tasks.ParallelLoopState.Break and System.Threading.Tasks.ParallelLoopState.Stop
        //     may not be used in combination by iterations of the same loop.
        public void Stop();
    }
}

ParallelLoopState - HowTo Use

In questo breve esempio di codice vedremo come utilizzarlo all’interno del nostro codice. Lo snippet è estratto da un codice scritto ad inizio dell’anno da me per un progetto avviato durante le festività. .Break()

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14

List<string> items = new List<string>(); 
List<string> result = null;

Parallel.For(0, items.Length, (idxItem, state) =>
{
	// code ...
	
	if (result != null)
	{
		state.Break();
	}
}

Come avrete visto utilizzare ParallelLoopState è davvero semplice e non richiede molto codice da scrivere. Sia chiaro che interrompere un Parallel.For (come ho fatto io nell’esempio) non garantisce certezze da quale item della lista sorgente verrà calcolato result. Nel mio caso bastava fosse verificata la condizione.