Parallelismo delle attività (runtime di concorrenza)

In questo documento viene descritto il ruolo delle attività e dei gruppi di attività nel runtime di concorrenza.Per attività si intende un'unità di lavoro che esegue un processo specifico.Un'attività in genere viene eseguita in parallelo con altre attività e può essere scomposta in aggiuntivo, più precise, attività.Un gruppo di attività organizza una raccolta di attività.

Utilizzare le attività quando si scrive codice asincrono e si desidera che alcune operazioni per verificare una volta terminata l'operazione asincrona.Ad esempio, è possibile utilizzare un'attività asincrona leggere da un file e da un'attività di continuazione, illustrato più avanti in questo documento, elaborare i dati dopo che diventa disponibile.Viceversa, gruppi di attività di utilizzo per scomporre lavoro parallelo in sezioni più piccole.Si supponga, ad esempio, di avere un algoritmo ricorsivo che divide il lavoro rimanente in due partizioni.È possibile utilizzare i gruppi di attività per eseguire tali partizioni contemporaneamente quindi attendere che il lavoro diviso per completare.

SuggerimentoSuggerimento

Quando si desidera applicare la stessa routine a ogni elemento di una raccolta in parallelo, utilizzare un algoritmo parallelo, come concurrency::parallel_for, anziché un'attività o un gruppo di attività.Per ulteriori informazioni sugli algoritmi paralleli, vedere Algoritmi paralleli.

Punti chiave

  • Quando si passano le variabili a un'espressione lambda in base al riferimento, è necessario garantire che tale variabile duri fino al completamento dell'attività.

  • Utilizzare le attività (la classe concurrency::task ) quando si scrive codice asincrono.

  • Utilizzare i gruppi di attività quali la classe concurrency::task_group o l'algoritmo concurrency::parallel_invoke ) quando è necessario scomporre il lavoro parallelo in sezioni più piccole e quindi attendere che più piccole per completare.

  • Utilizzare il metodo concurrency::task::then per creare le continuazioni.Una continuazione è un'attività che viene eseguito in modo asincrono dopo il completamento di un'altra attività.È possibile connettere un numero qualsiasi delle continuazioni per formare una catena di lavoro asincrono.

  • Una continuazione basata su attività è sempre pianificata per l'esecuzione quando l'attività precedente viene completata, anche quando l'attività precedente viene annullata o genera un'eccezione.

  • Utilizzare concurrency::when_all per creare un'attività che completa dopo ogni membro di un set di attività completa.Utilizzare concurrency::when_any per creare un'attività che completa dopo che un membro di un set di attività completa.

  • Le attività e i gruppi di attività possono partecipare al meccanismo di annullamento della libreria PPL.Per ulteriori informazioni, vedere Annullamento nella libreria PPL.

  • Per informazioni su come il runtime gestisce le eccezioni generate dalle attività e gruppi di attività, vedere Gestione delle eccezioni nel runtime di concorrenza.

In questo documento

  • Utilizzo di espressioni lambda

  • La classe di attività

  • Attività di continuazione

  • Basato su valore rispetto alle continuazioni Basate su attività

  • Creare attività

    • La funzione di when_all

    • La funzione when_any

  • Esecuzione posticipata di attività

  • Gruppi di attività

  • Confronto tra task_group e structured_task_group

  • Esempio

  • Programmazione robusta

Utilizzo di espressioni lambda

Le espressioni lambda sono un metodo comune per definire il lavoro che viene eseguito da attività e gruppi di attività a causa della sintassi concisa.Di seguito sono riportati alcuni suggerimenti sull'utilizzo:

  • Poiché in genere le attività vengono eseguite su thread in background, ricordare della durata degli oggetti in cui acquisite le variabili nelle espressioni lambda.Quando acquisire una variabile per valore, una copia della variabile viene eseguita nel corpo di lambda.Quando acquisite per riferimento, la copia non viene eseguita.Di conseguenza, assicurarsi che la durata di una variabile che acquisite per riferimento sopravvivete l'attività che utilizza.

  • Non includere in genere le variabili di tale vengono allocati nello stack.Ciò significa anche che non è necessario acquisire le variabili membro degli oggetti allocati nello stack.

  • Sia esplicito sulle variabili di acquisire le espressioni lambda per consentire l'identificazione cosa si di acquisizione per valore o per riferimento.Per questo motivo non è consigliabile utilizzare le opzioni [&] o [=] per le espressioni lambda.

Un modello comune è quando un'attività a una catena di continuazione viene assegnato a una variabile e un'altra attività leggere tale variabile.Non è possibile acquisire per valore perché ogni attività di continuazione utilizzerebbe una copia diverso di tale variabile.Per le variabili allocate applicativa, non è inoltre possibile acquisire per riferimento perché la variabile può non essere più validi.

Per risolvere questo problema, utilizzare un puntatore intelligente, come std::shared_ptr, per eseguire il wrapping della variabile e passare il puntatore intelligente per valore.In tal modo, l'oggetto sottostante può essere assegnato a e leggere e sopravvivrà le attività che lo utilizza.Utilizzare questa tecnica anche se la variabile è un puntatore o un handle riferimento contate (^a un oggetto di Windows Runtime.Di seguito è riportato un esempio di base:

// lambda-task-lifetime.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>
#include <string>

using namespace concurrency;
using namespace std;

task<wstring> write_to_string()
{
    // Create a shared pointer to a string that is 
    // assigned to and read by multiple tasks.
    // By using a shared pointer, the string outlives
    // the tasks, which can run in the background after
    // this function exits.
    auto s = make_shared<wstring>(L"Value 1");

    return create_task([s] 
    {
        // Print the current value.
        wcout << L"Current value: " << *s << endl;
        // Assign to a new value.
        *s = L"Value 2";

    }).then([s] 
    {
        // Print the current value.
        wcout << L"Current value: " << *s << endl;
        // Assign to a new value and return the string.
        *s = L"Value 3";
        return *s;
    });
}

int wmain()
{
    // Create a chain of tasks that work with a string.
    auto t = write_to_string();

    // Wait for the tasks to finish and print the result.
    wcout << L"Final value: " << t.get() << endl;
}

/* Output:
    Current value: Value 1
    Current value: Value 2
    Final value: Value 3
*/

Per ulteriori informazioni sulle espressioni lambda, vedere Espressioni lambda in C++.

[In alto]

La classe di attività

È possibile utilizzare la classe concurrency::task per creare le attività in serie di operazioni dipendenti.Questo modello di composizione è supportato dalla nozione delle continuazioni.Una continuazione consente al codice da eseguire quando il precedente, oppure antecedente, un'attività completa.Il risultato dell'attività precedente viene passato come input a uno o più attività di continuazione.Quando l'attività precedente viene completata, tutte le attività di continuazione in attesa su vengono fornite per l'esecuzione.Ogni attività di continuazione riceve una copia del risultato dell'attività precedente.A sua volta, tali attività di continuazione possono essere attività precedenti per le continuazioni, quindi creare una catena di attività.Le continuazioni consentono di creare le catene arbitrario lunghezzi le attività che dispongono di dipendenze specifiche.Inoltre, un'attività può partecipare all'annullamento o prima che le attività di avvio o in modo cooperativo in esecuzione.Per ulteriori informazioni su questo modello di annullamento, vedere Annullamento nella libreria PPL.

task è una classe modello.Il parametro di tipo T è il tipo di risultato fornito dall'attività.Questo tipo può essere void se l'attività non restituisce un valore.T non può utilizzare il modificatore const.

Quando si crea un'attività, fornita una funzione lavoro che esegue il corpo di un'attività.Questa funzione lavoro viene fornito sotto forma di funzione lambda, puntatore a funzione, o oggetto funzione.Per attendere il completamento di un'attività senza ottenere il risultato, chiamare il metodo concurrency::task::wait.Il metodo task::wait restituisce un valore concurrency::task_status che indica se l'attività è stata completata o annullata stata.Per ottenere il risultato dell'attività, chiamare il metodo concurrency::task::get.Questo metodo chiama task::wait per attendere l'attività completare e pertanto blocca l'esecuzione del thread corrente finché il risultato non è disponibile.

Di seguito viene illustrato come creare un'attività, attende il relativo risultato e visualizzarne il valore.Gli esempi della documentazione vengono utilizzate le funzioni lambda in quanto forniscono una sintassi più concisa.Tuttavia, è possibile utilizzare anche i puntatori a funzione e gli oggetti funzione quando si utilizzano le attività.

// basic-task.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
    // Create a task.
    task<int> t([]()
    {
        return 42;
    });

    // In this example, you don't necessarily need to call wait() because
    // the call to get() also waits for the result.
    t.wait();

    // Print the result.
    wcout << t.get() << endl;
}

/* Output:
    42
*/

La funzione concurrency::create_task consente di utilizzare la parola chiave auto anziché dichiarare un tipo.Ad esempio, si consideri il seguente codice che crea e visualizza la matrice di identità:

// create-task.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <string>
#include <iostream>
#include <array>

using namespace concurrency;
using namespace std;

int wmain()
{
    task<array<array<int, 10>, 10>> create_identity_matrix([]
    {
        array<array<int, 10>, 10> matrix;
        int row = 0;
        for_each(begin(matrix), end(matrix), [&row](array<int, 10>& matrixRow) 
        {
            fill(begin(matrixRow), end(matrixRow), 0);
            matrixRow[row] = 1;
            row++;
        });
        return matrix;
    });

    auto print_matrix = create_identity_matrix.then([](array<array<int, 10>, 10> matrix)
    {
        for_each(begin(matrix), end(matrix), [](array<int, 10>& matrixRow) 
        {
            wstring comma;
            for_each(begin(matrixRow), end(matrixRow), [&comma](int n) 
            {
                wcout << comma << n;
                comma = L", ";
            });
            wcout << endl;
        });
    });

    print_matrix.wait();
}
/* Output:
    1, 0, 0, 0, 0, 0, 0, 0, 0, 0
    0, 1, 0, 0, 0, 0, 0, 0, 0, 0
    0, 0, 1, 0, 0, 0, 0, 0, 0, 0
    0, 0, 0, 1, 0, 0, 0, 0, 0, 0
    0, 0, 0, 0, 1, 0, 0, 0, 0, 0
    0, 0, 0, 0, 0, 1, 0, 0, 0, 0
    0, 0, 0, 0, 0, 0, 1, 0, 0, 0
    0, 0, 0, 0, 0, 0, 0, 1, 0, 0
    0, 0, 0, 0, 0, 0, 0, 0, 1, 0
    0, 0, 0, 0, 0, 0, 0, 0, 0, 1
*/

È possibile utilizzare la funzione create_task per creare un'operazione equivalente.

auto create_identity_matrix = create_task([]
{
    array<array<int, 10>, 10> matrix;
    int row = 0;
    for_each(begin(matrix), end(matrix), [&row](array<int, 10>& matrixRow) 
    {
        fill(begin(matrixRow), end(matrixRow), 0);
        matrixRow[row] = 1;
        row++;
    });
    return matrix;
});

Se viene generata un'eccezione durante l'esecuzione di un'attività, il marshalling di runtime che eccezione della chiamata successiva a task::get o a task::wait, o su una continuazione relativa alle attività.Per ulteriori informazioni sul meccanismo di gestione delle eccezioni di attività, vedere Gestione delle eccezioni nel runtime di concorrenza.

Per un esempio che utilizza task, concurrency::task_completion_event, l'annullamento, vedere Procedura dettagliata: connessione tramite attività e richiesta HTTP XML (IXHR2).(Classe task_completion_event viene descritta più avanti in questo documento.)

SuggerimentoSuggerimento

Per visualizzare i dettagli specifici delle attività nelle applicazioni Windows Store, vedere Asynchronous programming in C++ e Creazione di operazioni asincrone in C++ per le applicazioni Windows Store.

[In alto]

Attività di continuazione

Nella programmazione asincrona spesso una determinata operazione asincrona, quando viene completata, richiama un'altra operazione a cui passa dati.In genere, questo viene eseguito utilizzando i metodi di callback.Nel runtime di concorrenza, la stessa funzionalità viene fornita dalle attività di continuazione.Un'attività di continuazione (anche nota più semplicemente come continuazione) è un'attività asincrona richiamata da un'altra attività, detta attività precedente, al completamento di quest'ultima.Utilizzando le continuazioni, è possibile:

  • Passare dati dall'attività precedente alla continuazione.

  • Specificare le esatte condizioni che devono verificarsi affinché la continuazione viene richiamato o non viene richiamata.

  • Annullare una continuazione prima che venga avviata o cooperativamente mentre è in esecuzione.

  • Fornire suggerimenti sulla continuazione deve essere pianificata.(Si applica a Windows Store applicazioni solo.Per ulteriori informazioni, vedere Creazione di operazioni asincrone in C++ per le applicazioni Windows Store.

  • Richiamare le continuazioni più dallo stesso precedente.

  • Richiamare una continuazione quando tutte o alcune attività precedenti vengono completate.

  • Concatenare continuazioni uno dopo l'altro a qualsiasi lunghezza.

  • Utilizzare una continuazione per gestire le eccezioni generate dal precedente.

Queste funzionalità consentono di eseguire una o più attività quando la prima attività completa.Ad esempio, è possibile creare una continuazione che assembla un file dopo la prima attività letto dal disco.

Il seguente esempio viene modificato l'esempio precedente per utilizzare il metodo concurrency::task::then per pianificare una continuazione che visualizza il valore dell'attività precedente quando è disponibile.

// basic-continuation.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
    auto t = create_task([]() -> int
    {
        return 42;
    });

    t.then([](int result)
    {
        wcout << result << endl;
    }).wait();

    // Alternatively, you can chain the tasks directly and
    // eliminate the local variable.
    /*create_task([]() -> int
    {
        return 42;
    }).then([](int result)
    {
        wcout << result << endl;
    }).wait();*/
}

/* Output:
    42
*/

È possibile concatenare e annidare le attività a qualsiasi lunghezza.Un'attività può disporre di più continuazioni.Nell'esempio seguente viene illustrata una catena di base di continuazione che viene incrementato il valore dell'attività precedente tre volte.

// continuation-chain.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
    auto t = create_task([]() -> int
    { 
        return 0;
    });

    // Create a lambda that increments its input value.
    auto increment = [](int n) { return n + 1; };

    // Run a chain of continuations and print the result.
    int result = t.then(increment).then(increment).then(increment).get();
    wcout << result << endl;
}

/* Output:
    3
*/

Una continuazione è possibile tornare un'altra attività.In assenza di annullamento, questa attività viene eseguita prima di procedere successiva.Questa tecnica è nota come non wrapping asincrono.Non wrapping asincrono è utile quando si desidera eseguire il lavoro aggiuntivo in background, ma non desidera l'attività corrente bloccare il thread corrente.(Questo è comune nelle applicazioni Windows Store, dove le continuazioni possono essere eseguite sul thread UI).Nell'esempio seguente vengono illustrate tre attività.La prima attività restituisce un'altra attività che viene eseguita prima di un'attività di continuazione.

// async-unwrapping.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
    auto t = create_task([]()
    {
        wcout << L"Task A" << endl;

        // Create an inner task that runs before any continuation
        // of the outer task.
        return create_task([]()
        {
            wcout << L"Task B" << endl;
        });
    });

    // Run and wait for a continuation of the outer task.
    t.then([]()
    {
        wcout << L"Task C" << endl;
    }).wait();
}

/* Output:
    Task A
    Task B
    Task C
*/
Nota importanteImportante

Quando una continuazione di un'attività restituisce un'unica attività annidata di tipo N, l'attività risultante contiene il tipo N, non task<N>e completa quando l'attività annidata completa.Ovvero la continuazione non esegue wrapping dell'attività annidata.

[In alto]

Basato su valore rispetto alle continuazioni Basate su attività

Dato un oggetto task il cui tipo restituito è T, è possibile specificare un valore di tipo T o task<T> alle attività di continuazione.Una continuazione che accetta il tipo T è nota come continuazione basata su valore.Una continuazione basata su valore viene fornita per l'esecuzione quando l'attività precedente viene completata senza errori e non viene annullata.Una continuazione che accetta il tipo task<T> mentre il parametro è noto come continuazione relativa alle attività.Una continuazione basata su attività è sempre pianificata per l'esecuzione quando l'attività precedente viene completata, anche quando l'attività precedente viene annullata o genera un'eccezione.È quindi possibile chiamare task::get per ottenere il risultato dell'attività precedente.Se l'attività precedente è stata annullata, task::get generato concurrency::task_canceled.Se l'attività precedente ha generato un'eccezione, rigenerate task::get di eccezione.Una continuazione relativa alle attività non è contrassegnata come annullata quando la relativa attività precedente viene annullata.

[In alto]

Creare attività

In questa sezione vengono descritte le funzioni concurrency::when_any e concurrency::when_all, che consentono di creare più attività per implementare modelli comuni.

Dd492427.collapse_all(it-it,VS.110).gifLa funzione di when_all

La funzione when_all produce un'attività che completa dopo che un set di attività completa.Questa funzione restituisce un oggetto std::vector che contiene il risultato di ogni attività nel set.Nell'esempio di base when_all utilizza per creare attività che rappresenta il completamento di altre tre attività.

// join-tasks.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <array>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
    // Start multiple tasks.
    array<task<void>, 3> tasks = 
    {
        create_task([] { wcout << L"Hello from taskA." << endl; }),
        create_task([] { wcout << L"Hello from taskB." << endl; }),
        create_task([] { wcout << L"Hello from taskC." << endl; })
    };

    auto joinTask = when_all(begin(tasks), end(tasks));

    // Print a message from the joining thread.
    wcout << L"Hello from the joining thread." << endl;

    // Wait for the tasks to finish.
    joinTask.wait();
}

/* Sample output:
    Hello from the joining thread.
    Hello from taskA.
    Hello from taskC.
    Hello from taskB.
*/

[!NOTA]

Le attività passata a when_all devono essere uniformi.Ovvero devono restituiscono tutti lo stesso tipo.

È inoltre possibile utilizzare la sintassi && per creare un'attività che completa dopo che un set di attività completa, come illustrato nell'esempio seguente.

auto t = t1 && t2; // same as when_all

È più comune da utilizzare una continuazione con when_all di eseguire l'azione dopo che un set di attività completa.Il seguente esempio viene modificato l'esempio precedente per stampare la somma di tre attività tale che ognuno un risultato int.

// Start multiple tasks.
array<task<int>, 3> tasks =
{
    create_task([]() -> int { return 88; }),
    create_task([]() -> int { return 42; }),
    create_task([]() -> int { return 99; })
};

auto joinTask = when_all(begin(tasks), end(tasks)).then([](vector<int> results)
{
    wcout << L"The sum is " 
          << accumulate(begin(results), end(results), 0)
          << L'.' << endl;
});

// Print a message from the joining thread.
wcout << L"Hello from the joining thread." << endl;

// Wait for the tasks to finish.
joinTask.wait();

/* Output:
    Hello from the joining thread.
    The sum is 229.
*/

In questo esempio, è possibile specificare task<vector<int>> per produrre una continuazione relativa alle attività.

Nota di avvisoAttenzione

Se una qualsiasi attività in un set di attività viene annullato o genera un'eccezione, when_all immediatamente completa senza attendere il completamento delle attività rimanenti completare.Se viene generata un'eccezione, il runtime genera nuovamente l'eccezione quando si chiama task::get o task::wait l'oggetto task che when_all restituisce.Se una classe genera di più attività, il runtime esegue uno.Pertanto, se si genera un'eccezione, assicurarsi che l'attesa tutte le attività.

[In alto]

Dd492427.collapse_all(it-it,VS.110).gifLa funzione when_any

La funzione when_any produce un'attività che completa quando la prima attività in un set di attività completa.Questa funzione restituisce un oggetto std::pair che contiene il risultato dell'attività completata e l'indice dell'attività nel set.

La funzione when_any è particolarmente utile negli scenari seguenti:

  • Operazioni ridondanti.Si consideri un algoritmo o un'operazione che possono essere eseguito in vari modi.È possibile utilizzare la funzione when_any per selezionare l'operazione che il termine innanzitutto quindi annullare le operazioni rimanenti.

  • Operazioni interfogliate.È possibile avviare più operazioni che tutti devono completare e utilizzare la funzione when_any per elaborare i risultati durante ogni operazione.Dopo un'operazione completa, è possibile avviare uno o più attività aggiuntive.

  • Operazioni strozzate.È possibile utilizzare la funzione when_any per estendere lo scenario precedente limitando il numero di operazioni simultanee.

  • Operazioni scade.È possibile utilizzare la funzione when_any per scegliere tra una o più attività e un'attività che completa dopo un momento specifico.

Come con when_all, è normale utilizzare una continuazione che ha when_any di eseguire l'azione quando i primi in un set di attività sono state completate.Nell'esempio di base when_any utilizza per creare attività che completa quando il primo di altre tre attività completa.

// select-task.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <array>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
    // Start multiple tasks.
    array<task<int>, 3> tasks = {
        create_task([]() -> int { return 88; }),
        create_task([]() -> int { return 42; }),
        create_task([]() -> int { return 99; })
    };

    // Select the first to finish.
    when_any(begin(tasks), end(tasks)).then([](pair<int, size_t> result)
    {
        wcout << "First task to finish returns "
              << result.first
              << L" and has index "
              << result.second
              << L'.' << endl;
    }).wait();
}

/* Sample output:
    First task to finish returns 42 and has index 1.
*/

In questo esempio, è possibile specificare task<pair<int, size_t>> per produrre una continuazione relativa alle attività.

[!NOTA]

Come con when_all, attività che viene passato a when_any devono restituiscono tutti lo stesso tipo.

È inoltre possibile utilizzare la sintassi || per creare un'attività che completa dopo la prima attività in un set di attività completa, come illustrato nell'esempio seguente.

auto t = t1 || t2; // same as when_any

[In alto]

Esecuzione posticipata di attività

È talvolta necessario ritardare l'esecuzione di un'attività fino a soddisfare una condizione, o per avviare un'attività in risposta a un evento esterno.Ad esempio, nella programmazione asincrona, potrebbe essere necessario avviare un'attività in risposta a un evento di completamento I/O.

Due modi per ottenere questo risultato è di utilizzare una continuazione o avviare un'attività e di attesa su un evento nella funzione lavoro dell'attività.Tuttavia, vi sono casi in cui non sono possibili utilizzare una di queste tecniche.Ad esempio, per creare una continuazione, è necessario disporre dell'attività precedente.Tuttavia, se non è l'attività precedente, è possibile creare un evento di completamento di attività e una catena successiva l'evento di completamento all'attività precedente quando diventa disponibile.Inoltre, poiché un'attività in attesa anche blocca un thread, è possibile utilizzare eventi di completamento di attività per eseguire il lavoro quando un'operazione asincrona e quindi libero un thread.

Le guide concurrency::task_completion_event semplificano la composizione delle attività.Come classe task, il parametro di tipo T è il tipo di risultato fornito dall'attività.Questo tipo può essere void se l'attività non restituisce un valore.T non può utilizzare il modificatore const.In genere, un oggetto task_completion_event viene fornito un thread o un'attività che lo segnaleranno se il valore di diventa disponibile.Contemporaneamente, una o più attività sono impostate come listener dell'evento.Quando viene impostato, le attività del listener completate e le continuazioni vengono fornite eseguire.

Per un esempio che utilizza task_completion_event per implementare un'attività che completa dopo che un ritardo, vedere Procedura: creare un'attività che viene completata dopo un ritardo.

[In alto]

Gruppi di attività

Un gruppo di attività organizza una raccolta di attività.I gruppi di attività inseriscono le attività in una coda di acquisizione del lavoro.L'utilità di pianificazione rimuove le attività da questa coda eseguendole nelle risorse di elaborazione disponibili.Dopo avere aggiunto le attività a un gruppo di attività, è possibile attendere il completamento di tutte le attività o l'annullamento delle attività che non sono ancora state avviate.

La libreria PPL utilizza le classi concurrency::structured_task_group e concurrency::task_group per rappresentare i gruppi di attività e la classe concurrency::task_handle per rappresentare le attività eseguite in questi gruppi.La classe task_handle incapsula il codice che esegue il lavoro.Come classe task, la funzione lavoro viene fornito sotto forma di funzione lambda, puntatore a funzione, o oggetto funzione.In genere non è necessario utilizzare direttamente gli oggetti task_handle,ma è possibile passare le funzioni lavoro a un gruppo di attività, il quale crea e gestisce gli oggetti task_handle.

La libreria PPL divide i gruppi di attività in due categorie: gruppi di attività non strutturate e gruppi di attività strutturate.La libreria PPL utilizza la classe task_group per rappresentare i gruppi di attività non strutturate e la classe structured_task_group per rappresentare i gruppi di attività strutturate.

Nota importanteImportante

La libreria PPL definisce inoltre l'algoritmo concurrency::parallel_invoke, che utilizza la classe structured_task_group per eseguire un set di attività in parallelo.Poiché l'algoritmo parallel_invoke presenta una sintassi più concisa, è consigliabile utilizzarlo in alternativa alla classe structured_task_group quando è possibile.Nell'argomento Algoritmi paralleli viene descritto più dettagliatamente parallel_invoke.

Utilizzare parallel_invoke quando si dispone di diverse attività indipendenti che si desidera eseguire contemporaneamente ed è necessario attendere il completamento di tutte le attività prima di continuare.Questa tecnica è spesso detto parallelismo di fork e join.Utilizzare task_group quando si dispone di diverse attività indipendenti che si desidera eseguire contemporaneamente ma è possibile attendere il completamento delle attività in un secondo momento.È possibile ad esempio aggiungere attività a un oggetto task_group e attendere il completamento delle attività in un'altra funzione o da parte di un altro thread.

I gruppi di attività supportano il concetto di annullamento.L'annullamento consente di segnalare a tutte le attività attive che si desidera annullare l'operazione globale.L'annullamento impedisce inoltre l'avvio delle attività che non sono ancora avviate.Per ulteriori informazioni sull'annullamento, vedere Annullamento nella libreria PPL.

Il runtime fornisce inoltre un modello di gestione delle eccezioni che consente di generare un'eccezione da un'attività e di gestire tale eccezione durante l'attesa del completamento del gruppo di attività associato.Per ulteriori informazioni sul modello di gestione delle eccezioni, vedere Gestione delle eccezioni nel runtime di concorrenza.

[In alto]

Confronto tra task_group e structured_task_group

Sebbene sia consigliabile utilizzare task_group o parallel_invoke anziché la classe structured_task_group, vi sono casi in cui si desidera utilizzare structured_task_group, ad esempio, quando si scrive un algoritmo parallelo che esegue un numero variabile di attività o che richiede il supporto per l'annullamento.In questa sezione vengono illustrate le differenze tra le classi task_group e structured_task_group.

La classe task_group è thread-safe.Di conseguenza è possibile aggiungere attività a un oggetto task_group da più thread e da attendere o annullare un oggetto task_group da più thread.La costruzione e la distruzione di un oggetto structured_task_group devono essere eseguite nello stesso ambito lessicale.Inoltre, tutte le operazioni su un oggetto structured_task_group devono essere eseguite nello stesso thread.L'eccezione a questa regola è rappresentata dai metodi concurrency::structured_task_group::is_canceling e concurrency::structured_task_group::cancel.Un'attività figlio può chiamare questi metodi per annullare il gruppo di attività padre o verificarne l'annullamento in qualsiasi momento.

È possibile eseguire attività aggiuntive su un oggetto task_group dopo avere chiamato il metodo concurrency::task_group::run_and_wait o concurrency::task_group::wait.Al contrario, non è possibile eseguire attività aggiuntive su un oggetto structured_task_group dopo che chiamano metodi concurrency::structured_task_group::run_and_wait o concurrency::structured_task_group::wait.

Poiché la classe structured_task_group non viene sincronizzata nei thread, ha molto meno sovraccarico di esecuzione della classe task_group.Pertanto, se il problema non richiede la pianificazione del lavoro da più thread e non è possibile utilizzare l'algoritmo parallel_invoke, la classe structured_task_group consente di scrivere un codice dalle prestazioni migliori.

Se si utilizza un oggetto structured_task_group all'interno di un altro oggetto structured_task_group, è necessario che l'oggetto interno venga completato ed eliminato prima del completamento dell'oggetto esterno.La classe task_group non richiede il completamento dei gruppi di attività annidate prima di completare il gruppo esterno.

I gruppi di attività non strutturate e i gruppi di attività strutturate vengono utilizzati con gli handle dell'attività in diversi modi.È possibile passare le funzioni lavoro direttamente a un oggetto task_group. L'oggetto task_group creerà e gestirà l'handle dell'attività automaticamente.Con la classe structured_task_group è necessario gestire un oggetto task_handle per ogni attività.Ogni oggetto task_handle deve rimanere valido per tutta la durata del relativo oggetto structured_task_group associato.Utilizzare la funzione concurrency::make_task per creare un oggetto task_handle, come illustrato nell'esempio di base:

// make-task-structure.cpp
// compile with: /EHsc
#include <ppl.h>

using namespace concurrency;

int wmain()
{
   // Use the make_task function to define several tasks.
   auto task1 = make_task([] { /*TODO: Define the task body.*/ });
   auto task2 = make_task([] { /*TODO: Define the task body.*/ });
   auto task3 = make_task([] { /*TODO: Define the task body.*/ });

   // Create a structured task group and run the tasks concurrently.

   structured_task_group tasks;

   tasks.run(task1);
   tasks.run(task2);
   tasks.run_and_wait(task3);
}

Per gestire gli handle dell'attività per i casi in cui è presente un numero variabile di attività, utilizzare una routine di allocazione dello stack come _malloca o una classe contenitore come std::vector.

task_group e structured_task_group supportano entrambi l'annullamento.Per ulteriori informazioni sull'annullamento, vedere Annullamento nella libreria PPL.

[In alto]

Esempio

Nell'esempio di base seguente viene illustrato l'utilizzo dei gruppi di attività.In questo esempio viene utilizzato l'algoritmo parallel_invoke per eseguire due attività contemporaneamente.Ogni attività aggiunge sottoattività a un oggetto task_group.Si noti che la classe task_group consente l'aggiunta di attività a più attività contemporaneamente.

// using-task-groups.cpp
// compile with: /EHsc
#include <ppl.h>
#include <sstream>
#include <iostream>

using namespace concurrency;
using namespace std;

// Prints a message to the console.
template<typename T>
void print_message(T t)
{
   wstringstream ss;
   ss << L"Message from task: " << t << endl;
   wcout << ss.str(); 
}

int wmain()
{  
   // A task_group object that can be used from multiple threads.
   task_group tasks;

   // Concurrently add several tasks to the task_group object.
   parallel_invoke(
      [&] {
         // Add a few tasks to the task_group object.
         tasks.run([] { print_message(L"Hello"); });
         tasks.run([] { print_message(42); });
      },
      [&] {
         // Add one additional task to the task_group object.
         tasks.run([] { print_message(3.14); });
      }
   );

   // Wait for all tasks to finish.
   tasks.wait();
}

Questo esempio produce l'output seguente:

Message from task: Hello
Message from task: 3.14
Message from task: 42

Poiché l'algoritmo parallel_invoke esegue le attività contemporaneamente, l'ordine dei messaggi di output potrebbe variare.

Per esempi completi che illustrano come utilizzare l'algoritmo parallel_invoke, vedere Procedura: utilizzare parallel_invoke per scrivere una routine di ordinamento in parallelo e Procedura: utilizzare parallel_invoke per eseguire operazioni in parallelo.Per un esempio completo in cui viene utilizzata la classe task_group per l'implementazione di future asincrone, vedere Procedura dettagliata: implementazione di future.

[In alto]

Programmazione robusta

Assicurarsi che la comprensione del ruolo dell'annullamento e la gestione delle eccezioni quando si utilizzano le attività, i gruppi di attività e algoritmi paralleli.Ad esempio, in un albero di lavoro parallelo l'annullamento di un'attività impedisce l'esecuzione delle attività figlio.Ciò può comportare problemi se una delle attività figlio esegue un'operazione importante per l'applicazione, ad esempio liberare una risorsa.Inoltre, se un'attività figlio genera un'eccezione, essa può propagarsi tramite un distruttore di oggetti e causare un comportamento non definito nell'applicazione.Per un esempio in cui vengono illustrati questi punti, vedere la sezione Understand how Cancellation and Exception Handling Affect Object Destruction nelle procedure consigliate del documento relativo alla libreria PPL.Per ulteriori informazioni sui modelli di annullamento e di gestione delle eccezioni nella libreria PPL, vedere Annullamento nella libreria PPL e Gestione delle eccezioni nel runtime di concorrenza.

[In alto]

Argomenti correlati

Titolo

Descrizione

Procedura: utilizzare parallel_invoke per scrivere una routine di ordinamento in parallelo

Viene illustrato come utilizzare l'algoritmo parallel_invoke per migliorare le prestazioni dell'algoritmo di ordinamento bitonico.

Procedura: utilizzare parallel_invoke per eseguire operazioni in parallelo

Viene illustrato come utilizzare l'algoritmo parallel_invoke per migliorare le prestazioni di un programma che esegue più operazioni in un'origine dati condivisa.

Procedura: creare un'attività che viene completata dopo un ritardo

Viene illustrato come utilizzare task, cancellation_token_source, cancellation_tokene le classi task_completion_event per creare un'attività che completa dopo un ritardo.

Procedura dettagliata: implementazione di future

Viene illustrato come combinare le funzionalità esistenti del runtime di concorrenza con funzionalità ancora più avanzate.

PPL (Parallel Patterns Library)

Viene descritta la libreria PPL che fornisce un modello di programmazione imperativa per lo sviluppo di applicazioni simultanee.

Riferimento

Classe task (runtime di concorrenza)

Classe task_completion_event

Funzione when_all

Funzione when_any

Classe task_group

Funzione parallel_invoke

Classe structured_task_group