Problemi potenziali nel parallelismo di dati e attivitàPotential Pitfalls in Data and Task Parallelism

In molti casi, Parallel.For e Parallel.ForEach possono offrire miglioramenti significativi delle prestazioni nei normali cicli sequenziali.In many cases, Parallel.For and Parallel.ForEach can provide significant performance improvements over ordinary sequential loops. Le operazioni necessarie per parallelizzare il ciclo comportano tuttavia delle complessità che possono determinare problemi che in un codice sequenziale sono meno frequenti o addirittura assenti.However, the work of parallelizing the loop introduces complexity that can lead to problems that, in sequential code, are not as common or are not encountered at all. In questo argomento sono elencati alcuni suggerimenti da tenere presenti quando si scrivono cicli paralleli.This topic lists some practices to avoid when you write parallel loops.

Non presupporre che l'approccio in parallelo sia sempre più veloceDo Not Assume That Parallel Is Always Faster

In determinati casi l'esecuzione di un ciclo parallelo potrebbe essere più lenta dell'equivalente sequenziale.In certain cases a parallel loop might run slower than its sequential equivalent. La regola generale di base è che per i cicli paralleli con poche iterazioni e con delegati dell'utente veloci raramente si verifica un aumento significativo della velocità di esecuzione.The basic rule of thumb is that parallel loops that have few iterations and fast user delegates are unlikely to speedup much. Poiché molti fattori influiscono sulle prestazioni, è comunque consigliabile misurare sempre i risultati effettivi.However, because many factors are involved in performance, we recommend that you always measure actual results.

Evitare di scrivere in percorsi di memoria condivisiAvoid Writing to Shared Memory Locations

Nel codice sequenziale spesso si eseguono operazioni di lettura e scrittura su variabili o campi di classe statici.In sequential code, it is not uncommon to read from or write to static variables or class fields. Tuttavia, ogni volta che più thread eseguono un accesso simultaneo a queste variabili, è molto probabile che si verifichino race condition.However, whenever multiple threads are accessing such variables concurrently, there is a big potential for race conditions. Anche se è possibile sincronizzare l'accesso alla variabile mediante l'utilizzo di blocchi, il costo di questa sincronizzazione può influire negativamente sulle prestazioni.Even though you can use locks to synchronize access to the variable, the cost of synchronization can hurt performance. È pertanto consigliabile evitare o almeno limitare il più possibile l'accesso allo stato condiviso in un ciclo parallelo.Therefore, we recommend that you avoid, or at least limit, access to shared state in a parallel loop as much as possible. Il modo migliore per eseguire questa operazione è usare gli overload di Parallel.For e Parallel.ForEach che usano una variabile System.Threading.ThreadLocal<T> per archiviare lo stato thread-local durante l'esecuzione del ciclo.The best way to do this is to use the overloads of Parallel.For and Parallel.ForEach that use a System.Threading.ThreadLocal<T> variable to store thread-local state during loop execution. Per altre informazioni, vedere Procedura: Scrivere un ciclo Parallel.For con variabili di thread locali e Procedura: Scrivere un ciclo Parallel.ForEach con variabili partition-local.For more information, see How to: Write a Parallel.For Loop with Thread-Local Variables and How to: Write a Parallel.ForEach Loop with Partition-Local Variables.

Evitare parallelizzazioni eccessiveAvoid Over-Parallelization

L'utilizzo dei cicli paralleli comporta costi di sovraccarico dovuti al partizionamento della raccolta di origine e alla sincronizzazione dei thread di lavoro.By using parallel loops, you incur the overhead costs of partitioning the source collection and synchronizing the worker threads. I vantaggi della parallelizzazione vengono limitati ulteriormente dal numero di processori nel computer.The benefits of parallelization are further limited by the number of processors on the computer. Non si ottiene alcun aumento di velocità eseguendo più thread con vincoli di calcolo in un unico processore.There is no speedup to be gained by running multiple compute-bound threads on just one processor. È pertanto fondamentale evitare la parallelizzazione eccessiva di un ciclo.Therefore, you must be careful not to over-parallelize a loop.

La situazione più comune in cui si verifica la parallelizzazione eccessiva è quando si utilizzano cicli annidati.The most common scenario in which over-parallelization can occur is in nested loops. Nella maggior parte dei casi è meglio parallelizzare solo il ciclo esterno, a meno che non sussista almeno una delle condizioni seguenti:In most cases, it is best to parallelize only the outer loop unless one or more of the following conditions apply:

  • È noto che il ciclo interno è molto lungo.The inner loop is known to be very long.

  • Si eseguono calcoli dispendiosi in ogni ordine.You are performing an expensive computation on each order. L'operazione mostrata nell'esempio non è dispendiosa.(The operation shown in the example is not expensive.)

  • È noto che il sistema di destinazione presenta un numero di processori sufficiente per gestire il numero di thread che verranno prodotti dalla parallelizzazione della query su cust.Orders.The target system is known to have enough processors to handle the number of threads that will be produced by parallelizing the query on cust.Orders.

In ogni caso, il miglior modo per determinare la forma ottimale della query è tramite lo svolgimento di test e misure.In all cases, the best way to determine the optimum query shape is to test and measure.

Evitare chiamate a metodi non thread-safeAvoid Calls to Non-Thread-Safe Methods

La scrittura in metodi di istanza non thread-safe da un ciclo parallelo può comportare un danneggiamento dei dati che può passare inosservato nel programma.Writing to non-thread-safe instance methods from a parallel loop can lead to data corruption which may or may not go undetected in your program. Può inoltre comportare la generazione di eccezioni.It can also lead to exceptions. L'esempio seguente mostra uno scenario in cui più thread tentano di chiamare simultaneamente il metodo FileStream.WriteByte. Tuttavia, la classe non supporta le chiamate simultanee.In the following example, multiple threads would be attempting to call the FileStream.WriteByte method simultaneously, which is not supported by the class.

FileStream fs = File.OpenWrite(path);
byte[] bytes = new Byte[10000000];
// ...
Parallel.For(0, bytes.Length, (i) => fs.WriteByte(bytes[i]));
Dim fs As FileStream = File.OpenWrite(filepath)
Dim bytes() As Byte
ReDim bytes(1000000)
' ...init byte array
Parallel.For(0, bytes.Length, Sub(n) fs.WriteByte(bytes(n)))

Limitare le chiamate ai metodi thread-safeLimit Calls to Thread-Safe Methods

La maggior parte dei metodi statici in .NET Framework è thread-safe e può essere chiamata simultaneamente da più thread.Most static methods in the .NET Framework are thread-safe and can be called from multiple threads concurrently. Tuttavia, anche in questi casi, la sincronizzazione da applicare può comportare un rallentamento significativo della query.However, even in these cases, the synchronization involved can lead to significant slowdown in the query.

Nota

Per verificare ciò basta inserire nelle query alcune chiamate a WriteLine.You can test for this yourself by inserting some calls to WriteLine in your queries. Anche se questo metodo viene utilizzato a scopo dimostrativo negli esempi della documentazione, è consigliabile evitare di utilizzarlo nei cicli paralleli, a meno che non sia necessario.Although this method is used in the documentation examples for demonstration purposes, do not use it in parallel loops unless necessary.

Tenere presente i problemi di affinità di threadBe Aware of Thread Affinity Issues

Alcune tecnologie, ad esempio l'interoperabilità COM per i componenti apartment a thread singolo (STA, Single-Threaded Apartment), Windows Form e Windows Presentation Foundation (WPF), impongono restrizioni di affinità di thread che richiedono l'esecuzione del codice in un thread specifico.Some technologies, for example, COM interoperability for Single-Threaded Apartment (STA) components, Windows Forms, and Windows Presentation Foundation (WPF), impose thread affinity restrictions that require code to run on a specific thread. Ad esempio, sia in Windows Form sia in WPF, l'accesso a un controllo può essere eseguito solo nel thread in cui è stato creato.For example, in both Windows Forms and WPF, a control can only be accessed on the thread on which it was created. Ciò significa, ad esempio, che non è possibile aggiornare un controllo elenco da un ciclo parallelo, a meno che non si configuri l'utilità di pianificazione del thread in modo che venga pianificato solo il thread UI.This means, for example, that you cannot update a list control from a parallel loop unless you configure the thread scheduler to schedule work only on the UI thread. Per ulteriori informazioni, vedere Procedura: pianificare il lavoro nel thread dell'interfaccia utente.For more information, see How to: Schedule Work on the User Interface (UI) Thread.

Prestare attenzione quando si attendono delegati chiamati da Parallel.InvokeUse Caution When Waiting in Delegates That Are Called by Parallel.Invoke

In determinate circostanze Task Parallel Library rende inline un'attività, ovvero viene eseguito sull'attività nel thread attualmente in esecuzione.In certain circumstances, the Task Parallel Library will inline a task, which means it runs on the task on the currently executing thread. Per ulteriori informazioni, vedere Task Schedulers. Questa ottimizzazione delle prestazioni può in alcuni casi condurre a un deadlock.(For more information, see Task Schedulers.) This performance optimization can lead to deadlock in certain cases. Due attività potrebbero ad esempio eseguire lo stesso codice di delegato, che segnala quando si verifica un evento, quindi attende che l'altra attività segnali un evento.For example, two tasks might run the same delegate code, which signals when an event occurs, and then waits for the other task to signal. Se la seconda attività viene resa inline nello stesso thread del primo, e il primo entra in un ciclo di attesa, la seconda attività non sarà mai in grado di segnalare il rispettivo evento.If the second task is inlined on the same thread as the first, and the first goes into a Wait state, the second task will never be able to signal its event. Per evitare questa situazione, è possibile specificare un timeout sull'operazione di attesa o utilizzare costruttori di thread espliciti per garantire che un'attività non blocchi l'altra.To avoid such an occurrence, you can specify a timeout on the Wait operation, or use explicit thread constructors to help ensure that one task cannot block the other.

Non presupporre che le iterazioni di Foreach, For e ForAll vengano eseguite sempre in paralleloDo Not Assume that Iterations of ForEach, For and ForAll Always Execute in Parallel

È importante tenere presente che le singole iterazioni in un ciclo For, ForEach o ForAll possono non necessariamente essere eseguite in parallelo.It is important to keep in mind that individual iterations in a For, ForEach or ForAll loop may but do not have to execute in parallel. È pertanto necessario evitare di scrivere codice la cui correttezza dipenda dall'esecuzione parallela delle iterazioni o dall'esecuzione delle iterazioni in un particolare ordine.Therefore, you should avoid writing any code that depends for correctness on parallel execution of iterations or on the execution of iterations in any particular order. Il codice seguente, ad esempio, è molto probabile che conduca a un deadlock:For example, this code is likely to deadlock:

ManualResetEventSlim mre = new ManualResetEventSlim();
Enumerable.Range(0, Environment.ProcessorCount * 100)
    .AsParallel()
    .ForAll((j) =>
        {
            if (j == Environment.ProcessorCount)
            {
                Console.WriteLine("Set on {0} with value of {1}",
                    Thread.CurrentThread.ManagedThreadId, j);
                mre.Set();
            }
            else
            {
                Console.WriteLine("Waiting on {0} with value of {1}",
                    Thread.CurrentThread.ManagedThreadId, j);
                mre.Wait();
            }
        }); //deadlocks
Dim mres = New ManualResetEventSlim()
Enumerable.Range(0, Environment.ProcessorCount * 100) _
.AsParallel() _
.ForAll(Sub(j)

            If j = Environment.ProcessorCount Then
                Console.WriteLine("Set on {0} with value of {1}",
                                  Thread.CurrentThread.ManagedThreadId, j)
                mres.Set()
            Else
                Console.WriteLine("Waiting on {0} with value of {1}",
                                  Thread.CurrentThread.ManagedThreadId, j)
                mres.Wait()
            End If
        End Sub) ' deadlocks

In questo esempio, un'unica iterazione imposta un evento e tutte le altre iterazioni attendono l'evento.In this example, one iteration sets an event, and all other iterations wait on the event. Nessuna delle iterazioni in attesa può essere completata fino a quando non viene completata l'iterazione di impostazione dell'evento.None of the waiting iterations can complete until the event-setting iteration has completed. È tuttavia possibile che le iterazioni in attesa blocchino tutti i thread utilizzati per eseguire il ciclo parallelo, prima che l'iterazione di impostazione dell'evento abbia avuto la possibilità di essere eseguita.However, it is possible that the waiting iterations block all threads that are used to execute the parallel loop, before the event-setting iteration has had a chance to execute. Ciò comporta un deadlock. L'iterazione di impostazione dell'evento non verrà mai eseguita e le iterazioni in attesa non verranno mai riattivate.This results in a deadlock – the event-setting iteration will never execute, and the waiting iterations will never wake up.

In particolare, l'avanzamento di un'iterazione di un ciclo parallelo non deve dipendere da un'altra iterazione del ciclo.In particular, one iteration of a parallel loop should never wait on another iteration of the loop to make progress. Se il ciclo parallelo decide di pianificare le iterazioni in sequenza ma nell'ordine opposto, si verificherà un deadlock.If the parallel loop decides to schedule the iterations sequentially but in the opposite order, a deadlock will occur.

Evitare di eseguire cicli paralleli sul thread UIAvoid Executing Parallel Loops on the UI Thread

È importante mantenere reattiva l'interfaccia utente dell'applicazione.It is important to keep your application's user interface (UI) responsive. Se un'operazione comporta lavoro sufficiente a garantire la parallelizzazione, è probabile che non debba essere eseguita sul thread UI.If an operation contains enough work to warrant parallelization, then it likely should not be run that operation on the UI thread. Il carico di lavoro dell'operazione dovrebbe invece essere ripartito in modo che l'operazione venga eseguita su un thread in background.Instead, it should offload that operation to be run on a background thread. Se ad esempio si desidera utilizzare un ciclo parallelo per calcolare dati di cui deve essere eseguito il rendering in un controllo dell'interfaccia utente, è consigliabile eseguire il ciclo all'interno di un'istanza dell'attività anziché direttamente in un gestore eventi dell'interfaccia utente.For example, if you want to use a parallel loop to compute some data that should then be rendered into a UI control, you should consider executing the loop within a task instance rather than directly in a UI event handler. Solo quando il calcolo principale è stato completato dovrebbe essere eseguito nuovamente il marshalling dell'aggiornamento dell'interfaccia utente nel thread UI.Only when the core computation has completed should you then marshal the UI update back to the UI thread.

Se si eseguono cicli paralleli sul thread UI, evitare di aggiornare controlli dell'interfaccia utente dall'interno del ciclo.If you do run parallel loops on the UI thread, be careful to avoid updating UI controls from within the loop. Se si prova ad aggiornare controlli dell'interfaccia utente dall'interno di un ciclo parallelo in esecuzione sul thread UI possono verificarsi un danneggiamento dello stato, eccezioni, aggiornamenti ritardati e deadlock, a seconda di come viene richiamato l'aggiornamento dell'Interfaccia utente.Attempting to update UI controls from within a parallel loop that is executing on the UI thread can lead to state corruption, exceptions, delayed updates, and even deadlocks, depending on how the UI update is invoked. Nell'esempio seguente il ciclo parallelo blocca il thread UI sul quale è in esecuzione fino a che non vengono completate tutte le iterazioni.In the following example, the parallel loop blocks the UI thread on which it’s executing until all iterations are complete. Se tuttavia un'iterazione del ciclo è in esecuzione su un thread in background (come può accadere per For), la chiamata al metodo Invoke comporta l'invio di un messaggio al thread UI e l'attesa da parte dei blocchi dell'elaborazione del messaggio.However, if an iteration of the loop is running on a background thread (as For may do), the call to Invoke causes a message to be submitted to the UI thread and blocks waiting for that message to be processed. Poiché il thread UI è bloccato nell'esecuzione di For, il messaggio non potrà mai essere elaborato e si verifica un deadlock del thread UI.Since the UI thread is blocked running the For, the message can never be processed, and the UI thread deadlocks.

private void button1_Click(object sender, EventArgs e)
{
    Parallel.For(0, N, i =>
    {
        // do work for i
        button1.Invoke((Action)delegate { DisplayProgress(i); });
    });
}
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

    Dim iterations As Integer = 20
    Parallel.For(0, iterations, Sub(x)
                                    Button1.Invoke(Sub()
                                                       DisplayProgress(x)
                                                   End Sub)
                                End Sub)
End Sub

Nell'esempio seguente viene mostrato come evitare il deadlock mediante l'esecuzione del ciclo in un'istanza dell'attività.The following example shows how to avoid the deadlock, by running the loop inside a task instance. Il thread UI non è bloccato dal ciclo e il messaggio può essere elaborato.The UI thread is not blocked by the loop, and the message can be processed.

private void button1_Click(object sender, EventArgs e)
{
    Task.Factory.StartNew(() =>
        Parallel.For(0, N, i =>
        {
            // do work for i
            button1.Invoke((Action)delegate { DisplayProgress(i); });
        })
         );
}
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

    Dim iterations As Integer = 20
    Task.Factory.StartNew(Sub() Parallel.For(0, iterations, Sub(x)
                                                                Button1.Invoke(Sub()
                                                                                   DisplayProgress(x)
                                                                               End Sub)
                                                            End Sub))
End Sub

Vedere ancheSee Also

Programmazione parallelaParallel Programming
Problemi potenziali dell'utilizzo di PLINQPotential Pitfalls with PLINQ
Documento contenente una panoramica dei modelli per la programmazione parallela, ovvero come comprendere e applicare modelli paralleli con .NET Framework 4Patterns for Parallel Programming: Understanding and Applying Parallel Patterns with the .NET Framework 4