Condividi tramite


Utilità di pianificazione delle attività

Le utilità di pianificazione delle attività sono rappresentate dalla classe System.Threading.Tasks.TaskScheduler. Un'utilità di pianificazione dell'attività consente di verificare che venga completato tutto il lavoro. L'utilità di pianificazione dell'attività predefinita si basa sul ThreadPool .NET Framework 4, che offre la funzionalità di work-stealing per il bilanciamento del carico, quella di inserimento/ritiro dei thread per la velocità effettiva massima e buone prestazioni complessive. Dovrebbe essere sufficiente per la maggior parte degli scenari. Se tuttavia si richiedono funzionalità speciali, è possibile creare un'utilità di pianificazione personalizzata e abilitarla per attività o query specifiche. Per ulteriori informazioni su come creare e utilizzare un'utilità di pianificazione personalizzata, vedere Procedura: creare un'utilità di pianificazione che limita il grado di concorrenza. Per altri esempi di utilità di pianificazione personalizzate, vedere Esempi di estensione parallela nel sito Web MSDN Code Gallery (la pagina potrebbe essere in inglese).

Utilità di pianificazione predefinita e il ThreadPool

L'utilità di pianificazione predefinita per Task Parallel Library e PLINQ utilizza il ThreadPool .NET Framework per mettere in coda ed eseguire il lavoro. In .NET Framework 4 ThreadPool utilizza le informazioni fornite dal tipo System.Threading.Tasks.Task per supportare in modo efficiente il parallelismo con granularità fine (unità di lavoro di breve durata), spesso rappresentato da query e attività parallele.

Code locali e coda globaledel ThreadPool

Come nelle versioni precedenti di .NET Framework, il ThreadPool gestisce una coda di lavoro FIFO (first-in, first-out) globale per i thread di ogni dominio di applicazione. Ogni volta che un programma chiama QueueUserWorkItem (o UnsafeQueueUserWorkItem), il lavoro viene inserito in questa coda condivisa e, infine, ne viene annullato l'accodamento sul thread successivo che diventa disponibile. In .NET Framework 4 la coda è stata migliorata per l'utilizzo di un algoritmo senza blocco simile alla classe ConcurrentQueue. Grazie a questa implementazione senza blocco, ThreadPool impiega meno tempo per l'accodamento e l'annullamento dell'accodamento degli elementi di lavoro. Questo vantaggio a livello di prestazioni viene offerto per tutti i programmi che utilizzano il ThreadPool.

Le attività di livello superiore, che sono attività che non sono state create nel contesto di un'altra attività, vengono inserite nella coda globale esattamente come qualsiasi altro elemento di lavoro. Le attività annidate o figlio, che sono state create nel contesto di un'altra attività, sono invece gestite in modo diverso. Un'attività figlio o annidata viene inserita in una coda locale che è specifica del thread sul quale è in esecuzione l'attività padre. L'attività padre può essere un'attività di livello superiore ma potrebbe anche essere il figlio di un'altra attività. Quando questo thread è pronto per altro lavoro, cerca prima nella coda locale. Se sono presenti elementi di lavoro in attesa, è possibile accedervi rapidamente. L'accesso alle code locali viene eseguito in base all'ordine LIFO (last-in, first-out) allo scopo di mantenere la località della cache e ridurre i conflitti. Per ulteriori informazioni sulle attività figlio e le attività annidate, vedere Attività annidate e attività figlio.

Nell'esempio seguente vengono mostrate alcune attività pianificate sulla coda globale e altre attività pianificate sulla coda locale.

Sub QueueTasks()

    ' TaskA is a top level task.
    Dim taskA = Task.Factory.StartNew(Sub()

                                          Console.WriteLine("I was enqueued on the thread pool's global queue.")

                                          ' TaskB is a nested task and TaskC is a child task. Both go to local queue.
                                          Dim taskB = New Task(Sub() Console.WriteLine("I was enqueued on the local queue."))
                                          Dim taskC = New Task(Sub() Console.WriteLine("I was enqueued on the local queue, too."),
                                                                  TaskCreationOptions.AttachedToParent)

                                          taskB.Start()
                                          taskC.Start()

                                      End Sub)
End Sub
void QueueTasks()
{
    // TaskA is a top level task.
    Task taskA = Task.Factory.StartNew( () =>
    {                
        Console.WriteLine("I was enqueued on the thread pool's global queue."); 

        // TaskB is a nested task and TaskC is a child task. Both go to local queue.
        Task taskB = new Task( ()=> Console.WriteLine("I was enqueued on the local queue."));
        Task taskC = new Task(() => Console.WriteLine("I was enqueued on the local queue, too."),
                                TaskCreationOptions.AttachedToParent);

        taskB.Start();
        taskC.Start();

    });
}

L'utilizzo di code locali non solo riduce la pressione sulla coda globale, ma consente anche di sfruttare i vantaggi della località dei dati. Gli elementi di lavoro nella coda locale fanno spesso riferimento alle strutture dei dati che sono fisicamente vicine tra loro nella memoria. In questi casi i dati si trovano già nella cache dopo che è stata eseguita la prima attività ed è possibile accedervi rapidamente. Sia Parallel LINQ (PLINQ) sia la classe Parallel utilizzano in maniera estensiva attività annidate e attività figlio e raggiungono velocità significative mediante le code di lavoro locali.

Work Stealing

In ThreadPool .NET Framework 4 è inoltre disponibili un algoritmo work-stealing che garantisce che non ci siano thread inattivi quando è ancora presente lavoro nelle code di altri thread. Quando un thread del pool di thread è pronto a ricevere altro lavoro, prima guarda l'intestazione della coda locale, quindi nella coda globale, infine nelle code locali di altri thread. Se trova un elemento di lavoro nella coda locale di un altro thread, prima applica l'euristica per assicurarsi di riuscire a eseguire il lavoro in modo efficiente. Se è in grado di eseguire il lavoro, annulla l'accodamento dell'elemento di lavoro dalla coda (nell'ordine FIFO). Ciò contribuisce a ridurre il conflitto su ogni coda locale e a mantenere la località dei dati. Questa architettura consente un funzionamento del bilanciamento del carico di .NET Framework 4 ThreadPool più efficace rispetto alle versioni precedenti.

Attività di lunga durata

È possibile evitare in modo esplicito l'inserimento di un'attività in una coda locale. Si supponga il caso di un particolare elemento di lavoro che sarà in esecuzione per un periodo di tempo relativamente lungo e probabilmente bloccherà tutti gli altri elementi di lavoro sulla coda locale. In questo caso è possibile specificare l'opzione LongRunning che suggerisce all'utilità di pianificazione che potrebbe essere necessario un thread aggiuntivo per l'attività, in modo che non venga bloccato lo stato di avanzamento degli altri thread o elementi di lavoro sulla coda locale. Questa opzione consente di evitare del tutto il ThreadPool, incluse le code globali e locali.

Inline delle attività

In alcuni casi, quando un'attività è in stato di attesa, è possibile che venga eseguita in modo sincrono sul thread che esegue l'operazione di attesa. Ciò migliora le prestazioni, in quanto elimina la necessità di un thread aggiuntivo grazie all'utilizzo del thread esistente che, in caso contrario, sarebbe stato bloccato. Per evitare errori dovuti alla reentrancy, l'inline delle attività si verifica solo quando la destinazione di attesa viene individuata nella coda locale del thread pertinente.

Specifica di un contesto di sincronizzazione

È possibile utilizzare il metodo TaskScheduler.FromCurrentSynchronizationContext per specificare che un'attività deve essere pianificata per l'esecuzione su un thread specifico. Ciò si rivela utile in framework quali Windows Form e Windows Presentation Foundation, in cui l'accesso agli oggetti dell'interfaccia utente spesso è limitato al codice in esecuzione sullo stesso thread sul quale è stato creato l'oggetto dell'interfaccia utente. Per ulteriori informazioni, vedere Procedura: pianificare il lavoro in un contesto di sincronizzazione specificato.

Vedere anche

Riferimenti

TaskScheduler

Concetti

Task Parallel Library

Procedura: pianificare il lavoro in un contesto di sincronizzazione specificato

Altre risorse

Procedura: creare un'utilità di pianificazione che limita il grado di concorrenza

Factory delle attività