Suggerimenti per l'utilizzo del threading gestitoManaged Threading Best Practices

Il multithreading richiede un'attenta programmazione.Multithreading requires careful programming. È possibile ridurre la complessità della maggior parte delle attività accodando le richieste di esecuzione tramite thread di pool di thread.For most tasks, you can reduce complexity by queuing requests for execution by thread pool threads. In questo argomento vengono analizzate situazioni più complesse, come il coordinamento del lavoro di più thread o la gestione di thread che effettuano un blocco.This topic addresses more difficult situations, such as coordinating the work of multiple threads, or handling threads that block.

Nota

A partire da .NET Framework 4, la libreria TPL (Task Parallel Library) e PLINQ specificano API che riducono, in parte, la complessità e i rischi associati alla programmazione multithread.Starting with the .NET Framework 4, the Task Parallel Library and PLINQ provide APIs that reduce some of the complexity and risks of multi-threaded programming. Per altre informazioni, vedere Programmazione parallela in .NET.For more information, see Parallel Programming in .NET.

Deadlocks e race conditionDeadlocks and Race Conditions

Il multithreading consente di risolvere problemi di trasmissione dei dati e velocità di risposta, ma è anche causa di nuovi problemi: i deadlock e le race condition.Multithreading solves problems with throughput and responsiveness, but in doing so it introduces new problems: deadlocks and race conditions.

DeadlockDeadlocks

Un deadlock si verifica quando uno di due thread tenta di bloccare una risorsa già bloccata dall'altro.A deadlock occurs when each of two threads tries to lock a resource the other has already locked. Nessuno dei due è in grado di fare ulteriori progressi.Neither thread can make any further progress.

È possibile rilevare i deadlock tramite i timeout disponibili in molti metodi delle classi di threading gestito.Many methods of the managed threading classes provide time-outs to help you detect deadlocks. Il codice riportato di seguito, ad esempio, prova ad acquisire un blocco su un oggetto denominato lockObject.For example, the following code attempts to acquire a lock on an object named lockObject. Se il blocco non viene ottenuto in 300 millisecondi, Monitor.TryEnter restituisce false.If the lock is not obtained in 300 milliseconds, Monitor.TryEnter returns false.

If Monitor.TryEnter(lockObject, 300) Then  
    Try  
        ' Place code protected by the Monitor here.  
    Finally  
        Monitor.Exit(lockObject)  
    End Try  
Else  
    ' Code to execute if the attempt times out.  
End If  
if (Monitor.TryEnter(lockObject, 300)) {  
    try {  
        // Place code protected by the Monitor here.  
    }  
    finally {  
        Monitor.Exit(lockObject);  
    }  
}  
else {  
    // Code to execute if the attempt times out.  
}  

Condizioni di tracciaRace Conditions

Una race condition è un bug che si verifica quando il risultato di un programma dipende da quale tra due o più thread raggiunge per primo un determinato blocco di codice.A race condition is a bug that occurs when the outcome of a program depends on which of two or more threads reaches a particular block of code first. L'esecuzione ripetuta del programma produce ogni volta risultati diversi che non è possibile prevedere in anticipo.Running the program many times produces different results, and the result of any given run cannot be predicted.

Un semplice esempio di race condition è l'incremento di un campo.A simple example of a race condition is incrementing a field. Si supponga che una classe possieda un campo statico (Shared in Visual Basic) che viene incrementato ogni volta che viene creata un'istanza della classe tramite codice come objCt++; (C#) o objCt += 1 (Visual Basic).Suppose a class has a private static field (Shared in Visual Basic) that is incremented every time an instance of the class is created, using code such as objCt++; (C#) or objCt += 1 (Visual Basic). Per eseguire questa operazione, è necessario caricare il valore da objCt in un registro, incrementare il valore e archiviarlo in objCt.This operation requires loading the value from objCt into a register, incrementing the value, and storing it in objCt.

In un'applicazione multithreading, un thread che abbia caricato e incrementato il valore potrebbe venire interrotto da un altro thread che esegue tutti e tre i passaggi. Quando il primo thread riprende l'esecuzione e archivia il valore, sovrascrive objCt anche se nel frattempo il valore è stato modificato.In a multithreaded application, a thread that has loaded and incremented the value might be preempted by another thread which performs all three steps; when the first thread resumes execution and stores its value, it overwrites objCt without taking into account the fact that the value has changed in the interim.

Questa particolare race condition è facilmente evitabile usando i metodi della classe Interlocked, ad esempio Interlocked.Increment.This particular race condition is easily avoided by using methods of the Interlocked class, such as Interlocked.Increment. Per informazioni sulle altre tecniche di sincronizzazione dei dati tra più thread, vedere Sincronizzazione dei dati per il multithreading.To read about other techniques for synchronizing data among multiple threads, see Synchronizing Data for Multithreading.

Le race condition possono verificarsi anche quando si sincronizzano le attività di più thread.Race conditions can also occur when you synchronize the activities of multiple threads. Quando si scrive una riga di codice, è necessario considerare le possibili conseguenze nel caso in cui un thread venisse interrotto prima dell'esecuzione della riga o di una qualsiasi delle singole istruzioni del computer che costituiscono la riga e un altro thread lo raggiungesse.Whenever you write a line of code, you must consider what might happen if a thread were preempted before executing the line (or before any of the individual machine instructions that make up the line), and another thread overtook it.

Numero di processoriNumber of Processors

La maggior parte dei computer possiede più processori (detti anche core), anche i piccoli dispositivi come tablet e telefoni.Most computers now have multiple processors (also called cores), even small devices such as tablets and phones. Se si sta sviluppando un software che funzionerà anche nei computer a processore singolo, si noti che il multithreading consente di risolvere diversi problemi per i computer a processore singolo e multiprocessore.If you know you're developing software that will also run on single-processor computers, you should be aware that multithreading solves different problems for single-processor computers and multiprocessor computers.

Computer multiprocessoreMultiprocessor Computers

Il multithreading consente di migliorare la velocità effettiva.Multithreading provides greater throughput. Dieci processori possono decuplicare il lavoro di uno, ma solo a condizione che il lavoro sia suddiviso in modo tale che tutti e dieci possano funzionare contemporaneamente. I thread consentono di suddividere facilmente il lavoro e di sfruttare l'ulteriore capacità di elaborazione.Ten processors can do ten times the work of one, but only if the work is divided so that all ten can be working at once; threads provide an easy way to divide the work and exploit the extra processing power. Se si usa il multithreading su un computer multiprocessore:If you use multithreading on a multiprocessor computer:

  • Il numero di thread che è possibile eseguire contemporaneamente dipende dal numero dei processori.The number of threads that can execute concurrently is limited by the number of processors.

  • Un thread in background viene eseguito solo quando il numero di thread in primo piano in esecuzione è inferiore al numero dei processori.A background thread executes only when the number of foreground threads executing is smaller than the number of processors.

  • Quando si chiama il metodo Thread.Start su un thread, l'avvio immediato dell'esecuzione dipende dal numero di processori e thread attualmente in attesa di esecuzione.When you call the Thread.Start method on a thread, that thread might or might not start executing immediately, depending on the number of processors and the number of threads currently waiting to execute.

  • Le race condition possono verificarsi non solo perché i thread vengono interrotti inaspettatamente, ma anche perché è possibile che due thread in esecuzione su processori diversi competano per raggiungere lo stesso blocco di codice.Race conditions can occur not only because threads are preempted unexpectedly, but because two threads executing on different processors might be racing to reach the same code block.

Computer a processore singoloSingle-Processor Computers

Il multithreading consente di rendere più rapida la risposta all'utente del computer e di usare il tempo di inattività per attività in background.Multithreading provides greater responsiveness to the computer user, and uses idle time for background tasks. Se si usa il multithreading su un computer a processore singolo:If you use multithreading on a single-processor computer:

  • Viene sempre eseguito un solo thread per volta.Only one thread runs at any instant.

  • Viene eseguito un thread in background solo quando il thread utente principale è inattivo.A background thread executes only when the main user thread is idle. Un thread in primo piano costantemente in esecuzione priva i thread in background del tempo del processore.A foreground thread that executes constantly starves background threads of processor time.

  • Quando si chiama il metodo Thread.Start su un thread, quest'ultimo non viene eseguito finché il thread corrente non viene generato o interrotto dal sistema operativo.When you call the Thread.Start method on a thread, that thread does not start executing until the current thread yields or is preempted by the operating system.

  • Le race condition si verificano in genere perché il programmatore non ha previsto che un thread potesse venire interrotto in un momento inopportuno, consentendo talvolta a un altro thread di raggiungere per primo un blocco di codice.Race conditions typically occur because the programmer did not anticipate the fact that a thread can be preempted at an awkward moment, sometimes allowing another thread to reach a code block first.

Membri e costruttori staticiStatic Members and Static Constructors

Una classe non viene inizializzata finché non termina l'esecuzione del relativo costruttore (costruttore static in C#, Shared Sub New in Visual Basic).A class is not initialized until its class constructor (static constructor in C#, Shared Sub New in Visual Basic) has finished running. Per impedire l'esecuzione di codice su un tipo non inizializzato, Common Language Runtime blocca tutte le chiamate degli altri thread ai membri static della classe (membri Shared in Visual Basic) fino al termine dell'esecuzione del costruttore.To prevent the execution of code on a type that is not initialized, the common language runtime blocks all calls from other threads to static members of the class (Shared members in Visual Basic) until the class constructor has finished running.

Se ad esempio il costruttore di una classe avvia un nuovo thread e la routine del thread chiama un membro static della classe, il nuovo thread si bloccherà fino al completamento dell'esecuzione del costruttore.For example, if a class constructor starts a new thread, and the thread procedure calls a static member of the class, the new thread blocks until the class constructor completes.

Ciò è valido per qualsiasi tipo che può avere un costruttore static.This applies to any type that can have a static constructor.

Suggerimenti generaliGeneral Recommendations

Quando si usano più thread, attenersi alle seguenti linee guida:Consider the following guidelines when using multiple threads:

  • Non usare Thread.Abort per terminare altri thread.Don't use Thread.Abort to terminate other threads. Chiamare Abort su un altro thread equivale a generare un'eccezione su tale thread, senza conoscere il punto raggiunto dal thread nell'elaborazione.Calling Abort on another thread is akin to throwing an exception on that thread, without knowing what point that thread has reached in its processing.

  • Non usare Thread.Suspend e Thread.Resume per sincronizzare le attività di più thread.Don't use Thread.Suspend and Thread.Resume to synchronize the activities of multiple threads. Usare Mutex, ManualResetEvent, AutoResetEvent e Monitor.Do use Mutex, ManualResetEvent, AutoResetEvent, and Monitor.

  • Non controllare l'esecuzione dei thread in funzione dal programma principale, ad esempio tramite gli eventi.Don't control the execution of worker threads from your main program (using events, for example). Progettare invece il programma in modo che i thread in funzione siano responsabili di attendere finché il lavoro non è disponibile, eseguirlo e informare gli altri componenti del programma del termine dell'esecuzione.Instead, design your program so that worker threads are responsible for waiting until work is available, executing it, and notifying other parts of your program when finished. Se i thread di lavoro non consentono il blocco, prendere in considerazione l'uso di thread del pool di thread.If your worker threads do not block, consider using thread pool threads. Monitor.PulseAll è utile nelle situazioni in cui i thread di lavoro si bloccano.Monitor.PulseAll is useful in situations where worker threads block.

  • Non usare tipi come oggetti di blocco.Don't use types as lock objects. Ciò significa evitare codice come lock(typeof(X)) in C# o SyncLock(GetType(X)) in Visual Basic oppure l'uso di Monitor.Enter con oggetti Type.That is, avoid code such as lock(typeof(X)) in C# or SyncLock(GetType(X)) in Visual Basic, or the use of Monitor.Enter with Type objects. Per un determinato tipo, è disponibile una sola istanza di System.Type per ogni dominio dell'applicazione.For a given type, there is only one instance of System.Type per application domain. Se il tipo per cui si acquisisce un blocco è pubblico, anche codice diverso dal proprio può acquisire blocchi su di esso, con il conseguente verificarsi di deadlock.If the type you take a lock on is public, code other than your own can take locks on it, leading to deadlocks. Per informazioni su altre problematiche, vedere Reliability Best Practices (Procedure consigliate per l'ottimizzazione dell'affidabilità).For additional issues, see Reliability Best Practices.

  • Procedere con cautela in caso di blocco sulle istanze, ad esempio lock(this) in C# o SyncLock(Me) in Visual Basic.Use caution when locking on instances, for example lock(this) in C# or SyncLock(Me) in Visual Basic. Se altro codice dell'applicazione, esterno al tipo, acquisisce un blocco sull'oggetto, potrebbero verificarsi situazioni di deadlock.If other code in your application, external to the type, takes a lock on the object, deadlocks could occur.

  • Assicurarsi che un thread entrato in un monitor lasci sempre il monitor, anche se si verifica un'eccezione mentre il thread si trova nel monitor.Do ensure that a thread that has entered a monitor always leaves that monitor, even if an exception occurs while the thread is in the monitor. L'istruzione lock di C# e l'istruzione SyncLock di Visual Basic generano automaticamente questo comportamento, impiegando un blocco finally per garantire che venga chiamato Monitor.Exit.The C# lock statement and the Visual Basic SyncLock statement provide this behavior automatically, employing a finally block to ensure that Monitor.Exit is called. Se non è possibile garantire che Exit verrà chiamato, modificare la progettazione in modo da usare Mutex.If you cannot ensure that Exit will be called, consider changing your design to use Mutex. Un mutex viene rilasciato automaticamente quando il thread che ne è attualmente proprietario termina.A mutex is automatically released when the thread that currently owns it terminates.

  • Usare più thread per le attività che richiedono risorse diverse ed evitare di assegnare più thread a una sola risorsa.Do use multiple threads for tasks that require different resources, and avoid assigning multiple threads to a single resource. Alcune attività, ad esempio, che hanno un proprio thread, usufruiscono dei vantaggi di I/O, poiché il thread si bloccherà durante le operazioni di I/O consentendo l'esecuzione di altri thread.For example, any task involving I/O benefits from having its own thread, because that thread will block during I/O operations and thus allow other threads to execute. Anche l'input utente è una risorsa che trae vantaggio da un thread dedicato.User input is another resource that benefits from a dedicated thread. In un computer a processore unico, un'attività che comporta un calcolo a elevato utilizzo di risorse coesiste con l'input utente e con attività che coinvolgono I/O, ma più attività con un elevato utilizzo di risorse competono fra loro.On a single-processor computer, a task that involves intensive computation coexists with user input and with tasks that involve I/O, but multiple computation-intensive tasks contend with each other.

  • Si prenda in considerazione l'uso dei metodi della classe Interlocked per le modifiche semplici allo stato, invece dell'uso dell'istruzione lock (SyncLock in Visual Basic).Consider using methods of the Interlocked class for simple state changes, instead of using the lock statement (SyncLock in Visual Basic). L'istruzione lock è un valido strumento generico, ma la classe Interlocked offre prestazioni migliori per gli aggiornamenti che devono essere atomici.The lock statement is a good general-purpose tool, but the Interlocked class provides better performance for updates that must be atomic. Internamente esegue un unico prefisso lock se non esistono conflitti.Internally, it executes a single lock prefix if there is no contention. Nelle analisi di codice controllare il codice simile a quello indicato negli esempi riportati di seguito.In code reviews, watch for code like that shown in the following examples. Nel primo esempio viene incrementata una variabile di stato:In the first example, a state variable is incremented:

    SyncLock lockObject  
        myField += 1  
    End SyncLock  
    
    lock(lockObject)   
    {  
        myField++;  
    }  
    

    Per migliorare le prestazioni, è possibile usare il metodo Increment invece dell'istruzione lock, come illustrato di seguito:You can improve performance by using the Increment method instead of the lock statement, as follows:

    System.Threading.Interlocked.Increment(myField)  
    
    System.Threading.Interlocked.Increment(myField);  
    

    Nota

    In .NET Framework versione 2.0 il metodo Add offre aggiornamenti atomici in incrementi maggiori di 1.In the .NET Framework version 2.0, the Add method provides atomic updates in increments larger than 1.

    Nel secondo esempio una variabile del tipo di riferimento viene aggiornata solo se corrisponde a un riferimento Null (Nothing in Visual Basic).In the second example, a reference type variable is updated only if it is a null reference (Nothing in Visual Basic).

    If x Is Nothing Then  
        SyncLock lockObject  
            If x Is Nothing Then  
                x = y  
            End If  
        End SyncLock  
    End If  
    
    if (x == null)  
    {  
        lock (lockObject)  
        {  
            if (x == null)  
            {  
                x = y;  
            }  
        }  
    }  
    

    Per migliorare le prestazioni, è invece possibile usare il metodo CompareExchange, come indicato di seguito:Performance can be improved by using the CompareExchange method instead, as follows:

    System.Threading.Interlocked.CompareExchange(x, y, Nothing)  
    
    System.Threading.Interlocked.CompareExchange(ref x, y, null);  
    

    Nota

    In .NET Framework versione 2.0 il metodo CompareExchange presenta un overload generico che può essere usato per la sostituzione indipendente dai tipi di qualsiasi tipo di riferimento.In the .NET Framework version 2.0, the CompareExchange method has a generic overload that can be used for type-safe replacement of any reference type.

Suggerimenti per le librerie di classiRecommendations for Class Libraries

Si prendano in considerazione le linee guida riportate di seguito per la progettazione di librerie di classi per il multithreading:Consider the following guidelines when designing class libraries for multithreading:

  • Se possibile, evitare che sia necessario eseguire la sincronizzazione.Avoid the need for synchronization, if possible. Ciò è valido soprattutto nel caso di codice di uso più frequente.This is especially true for heavily used code. È ad esempio possibile modificare un algoritmo per tollerare una race condition piuttosto che per eliminarla.For example, an algorithm might be adjusted to tolerate a race condition rather than eliminate it. L'esecuzione di operazioni di sincronizzazione non necessarie riduce le prestazioni e crea la possibilità di deadlock e race condition.Unnecessary synchronization decreases performance and creates the possibility of deadlocks and race conditions.

  • Rendere i dati statici (Shared in Visual Basic) thread-safe per impostazione predefinita.Make static data (Shared in Visual Basic) thread safe by default.

  • Non rendere i dati di istanza thread-safe per impostazione predefinita.Do not make instance data thread safe by default. Se si aggiungono blocchi per creare codice thread-safe le prestazioni vengono ridotte, aumentano i conflitti di blocco ed è possibile che si verifichino deadlock.Adding locks to create thread-safe code decreases performance, increases lock contention, and creates the possibility for deadlocks to occur. Nei modelli applicativi comuni, solo un thread per volta esegue il codice utente riducendo la necessità di thread safety.In common application models, only one thread at a time executes user code, which minimizes the need for thread safety. Per questo motivo le librerie di classi di .NET Framework non sono thread-safe per impostazione predefinita.For this reason, the .NET Framework class libraries are not thread safe by default.

  • Evitare di specificare metodi statici che modificano lo stato statico.Avoid providing static methods that alter static state. Negli scenari server comuni, lo stato statico viene condiviso tra le richieste e, pertanto, più thread possono eseguire contemporaneamente tale codice.In common server scenarios, static state is shared across requests, which means multiple threads can execute that code at the same time. In questo modo è possibile che si verifichino bug dei thread.This opens up the possibility of threading bugs. È consigliabile provare a usare un modello di progettazione che incapsula i dati nelle istanze che non sono condivise tra le richieste.Consider using a design pattern that encapsulates data into instances that are not shared across requests. Se si sincronizzano i dati statici, inoltre, le chiamate tra i metodi statici che modificano lo stato possono determinare deadlock o sincronizzazione ridondante e influire negativamente sulle prestazioni.Furthermore, if static data are synchronized, calls between static methods that alter state can result in deadlocks or redundant synchronization, adversely affecting performance.

Vedere ancheSee Also

ThreadingThreading
Threads and Threading (Thread e threading)Threads and Threading