Programmazione asincrona basata su attività

La libreria Task Parallel Library (TPL) si basa sul concetto di attività, che rappresenta un'operazione asincrona. Per certi versi, un'attività è analoga a un thread o a un elemento di lavoro ThreadPool, ma a un livello più generale di astrazione. L'espressione parallelismo delle attività fa riferimento a una o più attività indipendenti eseguite contemporaneamente. Le attività forniscono due vantaggi principali:

  • Utilizzo più efficiente e scalabile delle risorse di sistema.

    Dietro le quinte, le attività vengono accodate a ThreadPool, che è stato migliorato con algoritmi che determinano e modificano il numero di thread. Questi algoritmi forniscono il bilanciamento del carico per ottimizzare la velocità effettiva. Questo processo rende le attività relativamente leggere ed è possibile crearne molte in modo da ottenere un parallelismo accurato.

  • Maggior controllo a livello di codice rispetto a quello ottenibile usando un thread o un elemento di lavoro.

    Le attività e il framework in cui esse sono inserite forniscono un ampio set di API che supportano varie funzionalità tra cui attesa, annullamento, continuazioni, gestione affidabile delle eccezioni, stato dettagliato e pianificazione personalizzata.

Per entrambi i motivi, TPL è l'API preferita per la scrittura di codice multithreading, asincrono e parallelo in .NET.

Creazione ed esecuzione implicite di attività

Il metodo Parallel.Invoke rappresenta un modo pratico per eseguire simultaneamente un numero qualsiasi di istruzioni arbitrarie. Basta passare un delegato Action per ogni elemento di lavoro. Il modo più semplice per creare questi delegati è usare le espressioni lambda. L'espressione lambda può chiamare un metodo denominato o fornire il codice inline. Nell'esempio seguente viene mostrata una chiamata a Invoke di base che crea e avvia due attività in esecuzione simultanea. La prima attività è rappresentata da un'espressione lambda che chiama un metodo denominato DoSomeWork e la seconda attività è rappresentata da un'espressione lambda che chiama un metodo denominato DoSomeOtherWork.

Nota

Questa documentazione usa espressioni lambda per definire delegati in TPL. Se non si ha familiarità con le espressioni lambda in C# o Visual Basic, vedere Espressioni lambda in PLINQ e TPL.

Parallel.Invoke(() => DoSomeWork(), () => DoSomeOtherWork());
Parallel.Invoke(Sub() DoSomeWork(), Sub() DoSomeOtherWork())

Nota

Il numero di istanze Task create automaticamente da Invoke non è necessariamente uguale al numero dei delegati forniti. La libreria TPL potrebbe applicare varie ottimizzazioni, specialmente nel caso di un numero elevato di delegati.

Per altre informazioni, vedere Procedura: utilizzare Parallel.Invoke per eseguire operazioni in parallelo.

Per ottenere un controllo maggiore sull'esecuzione delle attività o per restituire un valore dall'attività,si deve utilizzare in modo più esplicito gli oggetti Task.

Creazione ed esecuzione esplicite di attività

Un'attività che non restituisce un valore è rappresentata dalla classe System.Threading.Tasks.Task. Un'attività che restituisce un valore viene rappresentata dalla classe System.Threading.Tasks.Task<TResult> che eredita da Task. L'oggetto attività gestisce i dettagli dell'infrastruttura e fornisce metodi e proprietà accessibili dal thread chiamante per tutta la durata dell'attività. Ad esempio, è possibile accedere in qualsiasi momento alla proprietà Status di un'attività per determinare se è stata avviata, eseguita fino al completamento o annullata oppure se ha generato un'eccezione. Lo stato è rappresentato da un'enumerazione TaskStatus.

Quando si crea un'attività si assegna ad essa un delegato dell'utente che incapsula il codice che verrà eseguito dall'attività. Il delegato può essere espresso come un delegato denominato, un metodo anonimo o un'espressione lambda. Le espressioni lambda possono contenere una chiamata a un metodo denominato, come mostrato nell'esempio seguente. L'esempio include una chiamata al metodo Task.Wait per garantire che l'attività venga completata prima che termini l'applicazione in modalità console.

using System;
using System.Threading;
using System.Threading.Tasks;

public class Lambda
{
   public static void Main()
   {
      Thread.CurrentThread.Name = "Main";

      // Create a task and supply a user delegate by using a lambda expression.
      Task taskA = new Task( () => Console.WriteLine("Hello from taskA."));
      // Start the task.
      taskA.Start();

      // Output a message from the calling thread.
      Console.WriteLine("Hello from thread '{0}'.",
                        Thread.CurrentThread.Name);
      taskA.Wait();
   }
}
// The example displays output as follows:
//       Hello from thread 'Main'.
//       Hello from taskA.
// or
//       Hello from taskA.
//       Hello from thread 'Main'.
Imports System.Threading

Namespace Lambda
    Module Example
        Public Sub Main()
            Thread.CurrentThread.Name = "Main"

            ' Create a task and supply a user delegate by using a lambda expression. 
            Dim taskA = New Task(Sub() Console.WriteLine("Hello from taskA."))
            ' Start the task.
            taskA.Start()

            ' Output a message from the calling thread.
            Console.WriteLine("Hello from thread '{0}'.",
                            Thread.CurrentThread.Name)
            taskA.Wait()
        End Sub
    End Module
    ' The example displays output like the following:
    '    Hello from thread 'Main'.
    '    Hello from taskA.
End Namespace

È anche possibile usare i metodi Task.Run per creare e avviare un'attività in un'unica operazione. Per gestire l'attività, i metodi Run utilizzano l'utilità di pianificazione delle attività predefinita, indipendentemente dall'utilità di pianificazione associata al thread corrente. I metodi Run rappresentano il modo preferito per creare e avviare le attività nei casi in cui non occorre un maggiore controllo sulla creazione e la pianificazione di attività.

using System;
using System.Threading;
using System.Threading.Tasks;

namespace Run;

public class Example
{
   public static void Main()
   {
      Thread.CurrentThread.Name = "Main";

      // Define and run the task.
      Task taskA = Task.Run( () => Console.WriteLine("Hello from taskA."));

      // Output a message from the calling thread.
      Console.WriteLine("Hello from thread '{0}'.",
                          Thread.CurrentThread.Name);
      taskA.Wait();
   }
}
// The example displays output as follows:
//       Hello from thread 'Main'.
//       Hello from taskA.
// or
//       Hello from taskA.
//       Hello from thread 'Main'.
Imports System.Threading

Namespace Run
    Module Example
        Public Sub Main()
            Thread.CurrentThread.Name = "Main"

            Dim taskA As Task = Task.Run(Sub() Console.WriteLine("Hello from taskA."))

            ' Output a message from the calling thread.
            Console.WriteLine("Hello from thread '{0}'.",
                            Thread.CurrentThread.Name)
            taskA.Wait()
        End Sub
    End Module
    ' The example displays output like the following:
    '    Hello from thread 'Main'.
    '    Hello from taskA.
End Namespace

È anche possibile usare il metodo TaskFactory.StartNew per creare e avviare un'attività in un'unica operazione. Come illustrato nell'esempio seguente, è possibile usare questo metodo quando:

  • Non è necessario separare la creazione e la pianificazione e richiedere opzioni aggiuntive per la creazione di attività o l'uso di un'utilità di pianificazione specifica.

  • È necessario passare uno stato aggiuntivo all'attività che è possibile recuperare tramite la relativa proprietà Task.AsyncState.

using System;
using System.Threading;
using System.Threading.Tasks;

namespace TaskIntro;

class CustomData
{
    public long CreationTime;
    public int Name;
    public int ThreadNum;
}

public class AsyncState
{
    public static void Main()
    {
        Task[] taskArray = new Task[10];
        for (int i = 0; i < taskArray.Length; i++)
        {
            taskArray[i] = Task.Factory.StartNew((Object obj) =>
            {
                CustomData data = obj as CustomData;
                if (data == null) return;

                data.ThreadNum = Thread.CurrentThread.ManagedThreadId;
            },
            new CustomData() { Name = i, CreationTime = DateTime.Now.Ticks });
        }
        Task.WaitAll(taskArray);
        foreach (var task in taskArray)
        {
            var data = task.AsyncState as CustomData;
            if (data != null)
                Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",
                                  data.Name, data.CreationTime, data.ThreadNum);
        }
    }
}
// The example displays output like the following:
//     Task #0 created at 635116412924597583, ran on thread #3.
//     Task #1 created at 635116412924607584, ran on thread #4.
//     Task #2 created at 635116412924607584, ran on thread #4.
//     Task #3 created at 635116412924607584, ran on thread #4.
//     Task #4 created at 635116412924607584, ran on thread #3.
//     Task #5 created at 635116412924607584, ran on thread #3.
//     Task #6 created at 635116412924607584, ran on thread #4.
//     Task #7 created at 635116412924607584, ran on thread #4.
//     Task #8 created at 635116412924607584, ran on thread #3.
//     Task #9 created at 635116412924607584, ran on thread #4.
Imports System.Threading

Namespace AsyncState
    Class CustomData
        Public CreationTime As Long
        Public Name As Integer
        Public ThreadNum As Integer
    End Class

    Module Example
        Public Sub Main()
            Dim taskArray(9) As Task
            For i As Integer = 0 To taskArray.Length - 1
                taskArray(i) = Task.Factory.StartNew(Sub(obj As Object)
                                                         Dim data As CustomData = TryCast(obj, CustomData)
                                                         If data Is Nothing Then Return

                                                         data.ThreadNum = Environment.CurrentManagedThreadId
                                                     End Sub,
                New CustomData With {.Name = i, .CreationTime = Date.Now.Ticks})
            Next
            Task.WaitAll(taskArray)

            For Each task In taskArray
                Dim data = TryCast(task.AsyncState, CustomData)
                If data IsNot Nothing Then
                    Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",
                                    data.Name, data.CreationTime, data.ThreadNum)
                End If
            Next
        End Sub
    End Module
    ' The example displays output like the following:
    '     Task #0 created at 635116412924597583, ran on thread #3.
    '     Task #1 created at 635116412924607584, ran on thread #4.
    '     Task #2 created at 635116412924607584, ran on thread #4.
    '     Task #3 created at 635116412924607584, ran on thread #4.
    '     Task #4 created at 635116412924607584, ran on thread #3.
    '     Task #5 created at 635116412924607584, ran on thread #3.
    '     Task #6 created at 635116412924607584, ran on thread #4.
    '     Task #7 created at 635116412924607584, ran on thread #4.
    '     Task #8 created at 635116412924607584, ran on thread #3.
    '     Task #9 created at 635116412924607584, ran on thread #4.
End Namespace

Task e Task<TResult> espongono ciascuna una proprietà statica Factory che restituisce un'istanza predefinita per TaskFactory, in modo che si possa chiamare il metodo come Task.Factory.StartNew(). Nell'esempio seguente, inoltre, poiché le attività sono di tipo System.Threading.Tasks.Task<TResult>, ognuna di esse presenta una proprietà Task<TResult>.Result pubblica che contiene il risultato del calcolo. Le attività vengono eseguite in modo asincrono e potrebbero essere completate in qualsiasi ordine. Se si accede alla proprietà Result prima che il calcolo venga completato, la proprietà blocca il thread chiamante finché il valore non è disponibile.

using System;
using System.Threading.Tasks;

public class Result
{
   public static void Main()
   {
        Task<Double>[] taskArray = { Task<Double>.Factory.StartNew(() => DoComputation(1.0)),
                                     Task<Double>.Factory.StartNew(() => DoComputation(100.0)),
                                     Task<Double>.Factory.StartNew(() => DoComputation(1000.0)) };

        var results = new Double[taskArray.Length];
        Double sum = 0;

        for (int i = 0; i < taskArray.Length; i++) {
            results[i] = taskArray[i].Result;
            Console.Write("{0:N1} {1}", results[i],
                              i == taskArray.Length - 1 ? "= " : "+ ");
            sum += results[i];
        }
        Console.WriteLine("{0:N1}", sum);
   }

   private static Double DoComputation(Double start)
   {
      Double sum = 0;
      for (var value = start; value <= start + 10; value += .1)
         sum += value;

      return sum;
   }
}
// The example displays the following output:
//        606.0 + 10,605.0 + 100,495.0 = 111,706.0

Namespace Result
    Module Example
        Public Sub Main()
            Dim taskArray() = {Task(Of Double).Factory.StartNew(Function() DoComputation(1.0)),
                Task(Of Double).Factory.StartNew(Function() DoComputation(100.0)),
                Task(Of Double).Factory.StartNew(Function() DoComputation(1000.0))}

            Dim results(taskArray.Length - 1) As Double
            Dim sum As Double

            For i As Integer = 0 To taskArray.Length - 1
                results(i) = taskArray(i).Result
                Console.Write("{0:N1} {1}", results(i),
                    If(i = taskArray.Length - 1, "= ", "+ "))
                sum += results(i)
            Next
            Console.WriteLine("{0:N1}", sum)
        End Sub

        Private Function DoComputation(start As Double) As Double
            Dim sum As Double
            For value As Double = start To start + 10 Step .1
                sum += value
            Next
            Return sum
        End Function
    End Module
    ' The example displays the following output:
    '       606.0 + 10,605.0 + 100,495.0 = 111,706.0
End Namespace

Per altre informazioni, vedere Procedura: Restituire un valore da un'attività.

Quando si usa un'espressione lambda per creare un delegato, si avrà accesso a tutte le variabili visibili in quel punto del codice sorgente. Tuttavia, in alcuni casi, in particolare all'interno dei cicli, un'espressione lambda non acquisisce la variabile come previsto. Acquisisce solo il riferimento della variabile, non il valore, perché si modifica dopo ogni iterazione. Nell'esempio che segue viene illustrato il problema. Passa un contatore di cicli a un'espressione lambda che crea un'istanza di un oggetto CustomData e usa il contatore di cicli come identificatore dell'oggetto. Come indica l'output dell'esempio, ogni oggetto CustomData dispone di un identificatore identico.

using System;
using System.Threading;
using System.Threading.Tasks;

namespace Example.Iterations;

class CustomData
{
   public long CreationTime;
   public int Name;
   public int ThreadNum;
}

public class IterationTwo
{
   public static void Main()
   {
      // Create the task object by using an Action(Of Object) to pass in the loop
      // counter. This produces an unexpected result.
      Task[] taskArray = new Task[10];
      for (int i = 0; i < taskArray.Length; i++) {
         taskArray[i] = Task.Factory.StartNew( (Object obj) => {
                                                 var data = new CustomData() {Name = i, CreationTime = DateTime.Now.Ticks};
                                                 data.ThreadNum = Thread.CurrentThread.ManagedThreadId;
                                                 Console.WriteLine("Task #{0} created at {1} on thread #{2}.",
                                                                   data.Name, data.CreationTime, data.ThreadNum);
                                               },
                                              i );
      }
      Task.WaitAll(taskArray);
   }
}
// The example displays output like the following:
//       Task #10 created at 635116418427727841 on thread #4.
//       Task #10 created at 635116418427737842 on thread #4.
//       Task #10 created at 635116418427737842 on thread #4.
//       Task #10 created at 635116418427737842 on thread #4.
//       Task #10 created at 635116418427737842 on thread #4.
//       Task #10 created at 635116418427737842 on thread #4.
//       Task #10 created at 635116418427727841 on thread #3.
//       Task #10 created at 635116418427747843 on thread #3.
//       Task #10 created at 635116418427747843 on thread #3.
//       Task #10 created at 635116418427737842 on thread #4.
Imports System.Threading

Namespace IterationsTwo
    Class CustomData
        Public CreationTime As Long
        Public Name As Integer
        Public ThreadNum As Integer
    End Class

    Module Example
        Public Sub Main()
            ' Create the task object by using an Action(Of Object) to pass in the loop
            ' counter. This produces an unexpected result.
            Dim taskArray(9) As Task
            For i As Integer = 0 To taskArray.Length - 1
                taskArray(i) = Task.Factory.StartNew(Sub(obj As Object)
                                                         Dim data As New CustomData With {.Name = i, .CreationTime = Date.Now.Ticks}
                                                         data.ThreadNum = Environment.CurrentManagedThreadId
                                                         Console.WriteLine("Task #{0} created at {1} on thread #{2}.",
                                                                         data.Name, data.CreationTime, data.ThreadNum)
                                                     End Sub,
                    i)
            Next
            Task.WaitAll(taskArray)
        End Sub
    End Module
    ' The example displays output like the following:
    '       Task #10 created at 635116418427727841 on thread #4.
    '       Task #10 created at 635116418427737842 on thread #4.
    '       Task #10 created at 635116418427737842 on thread #4.
    '       Task #10 created at 635116418427737842 on thread #4.
    '       Task #10 created at 635116418427737842 on thread #4.
    '       Task #10 created at 635116418427737842 on thread #4.
    '       Task #10 created at 635116418427727841 on thread #3.
    '       Task #10 created at 635116418427747843 on thread #3.
    '       Task #10 created at 635116418427747843 on thread #3.
    '       Task #10 created at 635116418427737842 on thread #4.
End Namespace

È possibile accedere al valore in ogni iterazione fornendo un oggetto stato a un'attività mediante il relativo costruttore. Nell'esempio seguente viene modificato l'esempio precedente usando il contatore di cicli durante la creazione dell'oggetto CustomData, che, a sua volta, viene passato all'espressione lambda. Come indicato dall'output dell'esempio, ogni oggetto CustomData dispone ora di un identificatore univoco in base al valore del contatore di cicli al momento della creazione di un'istanza dell'oggetto.

using System;
using System.Threading;
using System.Threading.Tasks;

class CustomData
{
   public long CreationTime;
   public int Name;
   public int ThreadNum;
}

public class IterationOne
{
   public static void Main()
   {
      // Create the task object by using an Action(Of Object) to pass in custom data
      // to the Task constructor. This is useful when you need to capture outer variables
      // from within a loop.
      Task[] taskArray = new Task[10];
      for (int i = 0; i < taskArray.Length; i++) {
         taskArray[i] = Task.Factory.StartNew( (Object obj ) => {
                                                  CustomData data = obj as CustomData;
                                                  if (data == null)
                                                     return;

                                                  data.ThreadNum = Thread.CurrentThread.ManagedThreadId;
                                                  Console.WriteLine("Task #{0} created at {1} on thread #{2}.",
                                                                   data.Name, data.CreationTime, data.ThreadNum);
                                               },
                                               new CustomData() {Name = i, CreationTime = DateTime.Now.Ticks} );
      }
      Task.WaitAll(taskArray);
   }
}
// The example displays output like the following:
//       Task #0 created at 635116412924597583 on thread #3.
//       Task #1 created at 635116412924607584 on thread #4.
//       Task #3 created at 635116412924607584 on thread #4.
//       Task #4 created at 635116412924607584 on thread #4.
//       Task #2 created at 635116412924607584 on thread #3.
//       Task #6 created at 635116412924607584 on thread #3.
//       Task #5 created at 635116412924607584 on thread #4.
//       Task #8 created at 635116412924607584 on thread #4.
//       Task #7 created at 635116412924607584 on thread #3.
//       Task #9 created at 635116412924607584 on thread #4.
Imports System.Threading

Namespace IterationsOne
    Class CustomData
        Public CreationTime As Long
        Public Name As Integer
        Public ThreadNum As Integer
    End Class

    Module Example
        Public Sub Main()
            ' Create the task object by using an Action(Of Object) to pass in custom data
            ' to the Task constructor. This is useful when you need to capture outer variables
            ' from within a loop. 
            Dim taskArray(9) As Task
            For i As Integer = 0 To taskArray.Length - 1
                taskArray(i) = Task.Factory.StartNew(Sub(obj As Object)
                                                         Dim data As CustomData = TryCast(obj, CustomData)
                                                         If data Is Nothing Then Return

                                                         data.ThreadNum = Environment.CurrentManagedThreadId
                                                         Console.WriteLine("Task #{0} created at {1} on thread #{2}.",
                                                                         data.Name, data.CreationTime, data.ThreadNum)
                                                     End Sub,
                    New CustomData With {.Name = i, .CreationTime = Date.Now.Ticks})
            Next
            Task.WaitAll(taskArray)
        End Sub
    End Module
    ' The example displays output like the following:
    '       Task #0 created at 635116412924597583 on thread #3.
    '       Task #1 created at 635116412924607584 on thread #4.
    '       Task #3 created at 635116412924607584 on thread #4.
    '       Task #4 created at 635116412924607584 on thread #4.
    '       Task #2 created at 635116412924607584 on thread #3.
    '       Task #6 created at 635116412924607584 on thread #3.
    '       Task #5 created at 635116412924607584 on thread #4.
    '       Task #8 created at 635116412924607584 on thread #4.
    '       Task #7 created at 635116412924607584 on thread #3.
    '       Task #9 created at 635116412924607584 on thread #4.
End Namespace

Questo stato viene passato come argomento al delegato dell'attività ed è accessibile dall'oggetto attività tramite la proprietà Task.AsyncState. L'esempio seguente è una variante dell'esempio precedente Usa la proprietà AsyncState per visualizzare informazioni sugli oggetti CustomData passati all'espressione lambda.

using System;
using System.Threading;
using System.Threading.Tasks;

namespace TaskIntro;

class CustomData
{
    public long CreationTime;
    public int Name;
    public int ThreadNum;
}

public class AsyncState
{
    public static void Main()
    {
        Task[] taskArray = new Task[10];
        for (int i = 0; i < taskArray.Length; i++)
        {
            taskArray[i] = Task.Factory.StartNew((Object obj) =>
            {
                CustomData data = obj as CustomData;
                if (data == null) return;

                data.ThreadNum = Thread.CurrentThread.ManagedThreadId;
            },
            new CustomData() { Name = i, CreationTime = DateTime.Now.Ticks });
        }
        Task.WaitAll(taskArray);
        foreach (var task in taskArray)
        {
            var data = task.AsyncState as CustomData;
            if (data != null)
                Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",
                                  data.Name, data.CreationTime, data.ThreadNum);
        }
    }
}
// The example displays output like the following:
//     Task #0 created at 635116412924597583, ran on thread #3.
//     Task #1 created at 635116412924607584, ran on thread #4.
//     Task #2 created at 635116412924607584, ran on thread #4.
//     Task #3 created at 635116412924607584, ran on thread #4.
//     Task #4 created at 635116412924607584, ran on thread #3.
//     Task #5 created at 635116412924607584, ran on thread #3.
//     Task #6 created at 635116412924607584, ran on thread #4.
//     Task #7 created at 635116412924607584, ran on thread #4.
//     Task #8 created at 635116412924607584, ran on thread #3.
//     Task #9 created at 635116412924607584, ran on thread #4.
Imports System.Threading

Namespace AsyncState
    Class CustomData
        Public CreationTime As Long
        Public Name As Integer
        Public ThreadNum As Integer
    End Class

    Module Example
        Public Sub Main()
            Dim taskArray(9) As Task
            For i As Integer = 0 To taskArray.Length - 1
                taskArray(i) = Task.Factory.StartNew(Sub(obj As Object)
                                                         Dim data As CustomData = TryCast(obj, CustomData)
                                                         If data Is Nothing Then Return

                                                         data.ThreadNum = Environment.CurrentManagedThreadId
                                                     End Sub,
                New CustomData With {.Name = i, .CreationTime = Date.Now.Ticks})
            Next
            Task.WaitAll(taskArray)

            For Each task In taskArray
                Dim data = TryCast(task.AsyncState, CustomData)
                If data IsNot Nothing Then
                    Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",
                                    data.Name, data.CreationTime, data.ThreadNum)
                End If
            Next
        End Sub
    End Module
    ' The example displays output like the following:
    '     Task #0 created at 635116412924597583, ran on thread #3.
    '     Task #1 created at 635116412924607584, ran on thread #4.
    '     Task #2 created at 635116412924607584, ran on thread #4.
    '     Task #3 created at 635116412924607584, ran on thread #4.
    '     Task #4 created at 635116412924607584, ran on thread #3.
    '     Task #5 created at 635116412924607584, ran on thread #3.
    '     Task #6 created at 635116412924607584, ran on thread #4.
    '     Task #7 created at 635116412924607584, ran on thread #4.
    '     Task #8 created at 635116412924607584, ran on thread #3.
    '     Task #9 created at 635116412924607584, ran on thread #4.
End Namespace

ID attività

Ogni attività riceve un ID di tipo Integer con cui viene identificata in modo univoco in un dominio applicazione e a cui è possibile accedere tramite la proprietà Task.Id. Questo ID è utile per visualizzare informazioni sull'attività nelle finestre Stack in parallelo e Attività in parallelo del debugger di Visual Studio. L'ID viene creato in modo pigro, il che significa che non viene creato fino a quando non viene richiesto. Pertanto, un'attività potrebbe avere un ID diverso ogni volta che viene eseguito il programma. Per altre informazioni su come visualizzare gli ID attività nel debugger, vedere Uso della finestra Attività e Utilizzo della finestra Stack in parallelo.

Opzioni di creazione delle attività

La maggior parte delle API che creano attività genera overload che accettano un parametro TaskCreationOptions. Quando si specifica una o più di queste opzioni si indica all'utilità di pianificazione il modo in cui pianificare l'attività nel pool di thread. Le opzioni potrebbero essere combinate usando un'operazione OR bit per bit.

Nell'esempio seguente viene illustrata un'attività che presenta le opzioni LongRunning e PreferFairness:

var task3 = new Task(() => MyLongRunningMethod(),
                    TaskCreationOptions.LongRunning | TaskCreationOptions.PreferFairness);
task3.Start();

Dim task3 = New Task(Sub() MyLongRunningMethod(),
                        TaskCreationOptions.LongRunning Or TaskCreationOptions.PreferFairness)
task3.Start()

Attività, thread e impostazioni cultura

Ogni thread dispone di impostazioni cultura nonché di impostazioni cultura dell'interfaccia utente associate e definite rispettivamente dalle proprietà Thread.CurrentCulture e Thread.CurrentUICulture. Le impostazioni cultura di un thread vengono usate in operazioni quali formattazione, analisi, ordinamento e confronto di stringhe. Le impostazioni cultura dell'interfaccia utente di un thread vengono usate per la ricerca delle risorse.

Le impostazioni cultura di sistema definiscono le impostazioni cultura predefinite e le impostazioni cultura dell'interfaccia utente di un thread. Tuttavia, è possibile specificare impostazioni cultura predefinite per tutti i thread in un dominio applicazione usando le proprietà CultureInfo.DefaultThreadCurrentCulture e CultureInfo.DefaultThreadCurrentUICulture. Se si impostano in modo esplicito le impostazioni cultura di un thread e si avvia un nuovo thread, il nuovo thread non eredita le impostazioni cultura del thread chiamante; le impostazioni cultura sono invece le impostazioni cultura predefinite del sistema. Tuttavia, nella programmazione basata su attività, le attività usano le impostazioni cultura del thread chiamante, anche se l'attività viene eseguita in modo asincrono in un thread diverso.

Nell'esempio seguente viene illustrato questo concetto. Cambia le impostazioni cultura correnti dell'app in francese (Francia). Se il francese (Francia) è già la lingua corrente, viene modificato in inglese (Stati Uniti). Viene quindi richiamato un delegato denominato formatDelegate che restituisce alcuni numeri formattati come valori di valuta nelle nuove impostazioni cultura. Se il delegato viene richiamato da un'attività in modo sincrono o asincrono, l'attività usa le impostazioni cultura del thread chiamante.

using System;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
       decimal[] values = { 163025412.32m, 18905365.59m };
       string formatString = "C2";
       Func<String> formatDelegate = () => { string output = String.Format("Formatting using the {0} culture on thread {1}.\n",
                                                                           CultureInfo.CurrentCulture.Name,
                                                                           Thread.CurrentThread.ManagedThreadId);
                                             foreach (var value in values)
                                                output += String.Format("{0}   ", value.ToString(formatString));

                                             output += Environment.NewLine;
                                             return output;
                                           };

       Console.WriteLine("The example is running on thread {0}",
                         Thread.CurrentThread.ManagedThreadId);
       // Make the current culture different from the system culture.
       Console.WriteLine("The current culture is {0}",
                         CultureInfo.CurrentCulture.Name);
       if (CultureInfo.CurrentCulture.Name == "fr-FR")
          Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
       else
          Thread.CurrentThread.CurrentCulture = new CultureInfo("fr-FR");

       Console.WriteLine("Changed the current culture to {0}.\n",
                         CultureInfo.CurrentCulture.Name);

       // Execute the delegate synchronously.
       Console.WriteLine("Executing the delegate synchronously:");
       Console.WriteLine(formatDelegate());

       // Call an async delegate to format the values using one format string.
       Console.WriteLine("Executing a task asynchronously:");
       var t1 = Task.Run(formatDelegate);
       Console.WriteLine(t1.Result);

       Console.WriteLine("Executing a task synchronously:");
       var t2 = new Task<String>(formatDelegate);
       t2.RunSynchronously();
       Console.WriteLine(t2.Result);
   }
}
// The example displays the following output:
//         The example is running on thread 1
//         The current culture is en-US
//         Changed the current culture to fr-FR.
//
//         Executing the delegate synchronously:
//         Formatting using the fr-FR culture on thread 1.
//         163 025 412,32 €   18 905 365,59 €
//
//         Executing a task asynchronously:
//         Formatting using the fr-FR culture on thread 3.
//         163 025 412,32 €   18 905 365,59 €
//
//         Executing a task synchronously:
//         Formatting using the fr-FR culture on thread 1.
//         163 025 412,32 €   18 905 365,59 €
Imports System.Globalization
Imports System.Threading

Module Example
    Public Sub Main()
        Dim values() As Decimal = {163025412.32D, 18905365.59D}
        Dim formatString As String = "C2"
        Dim formatDelegate As Func(Of String) = Function()
                                                    Dim output As String = String.Format("Formatting using the {0} culture on thread {1}.",
                                                                                         CultureInfo.CurrentCulture.Name,
                                                                                         Thread.CurrentThread.ManagedThreadId)
                                                    output += Environment.NewLine
                                                    For Each value In values
                                                        output += String.Format("{0}   ", value.ToString(formatString))
                                                    Next
                                                    output += Environment.NewLine
                                                    Return output
                                                End Function

        Console.WriteLine("The example is running on thread {0}",
                          Thread.CurrentThread.ManagedThreadId)
        ' Make the current culture different from the system culture.
        Console.WriteLine("The current culture is {0}",
                          CultureInfo.CurrentCulture.Name)
        If CultureInfo.CurrentCulture.Name = "fr-FR" Then
            Thread.CurrentThread.CurrentCulture = New CultureInfo("en-US")
        Else
            Thread.CurrentThread.CurrentCulture = New CultureInfo("fr-FR")
        End If
        Console.WriteLine("Changed the current culture to {0}.",
                          CultureInfo.CurrentCulture.Name)
        Console.WriteLine()

        ' Execute the delegate synchronously.
        Console.WriteLine("Executing the delegate synchronously:")
        Console.WriteLine(formatDelegate())

        ' Call an async delegate to format the values using one format string.
        Console.WriteLine("Executing a task asynchronously:")
        Dim t1 = Task.Run(formatDelegate)
        Console.WriteLine(t1.Result)

        Console.WriteLine("Executing a task synchronously:")
        Dim t2 = New Task(Of String)(formatDelegate)
        t2.RunSynchronously()
        Console.WriteLine(t2.Result)
    End Sub
End Module

' The example displays the following output:
'
'          The example is running on thread 1
'          The current culture is en-US
'          Changed the current culture to fr-FR.
'
'          Executing the delegate synchronously:
'          Formatting Imports the fr-FR culture on thread 1.
'          163 025 412,32 €   18 905 365,59 €
'
'          Executing a task asynchronously:
'          Formatting Imports the fr-FR culture on thread 3.
'          163 025 412,32 €   18 905 365,59 €
'
'          Executing a task synchronously:
'          Formatting Imports the fr-FR culture on thread 1.
'          163 025 412,32 €   18 905 365,59 €

Nota

Nelle versioni di .NET Framework precedenti a .NET Framework 4.6, le impostazioni cultura di un'attività sono determinate dalle impostazioni cultura del thread in cui viene eseguito, non dalle impostazioni cultura del thread chiamante. Per le attività asincrone, le impostazioni cultura usate dall'attività potrebbero essere diverse dalle impostazioni cultura del thread chiamante.

Per altre informazioni sulle attività asincrone e sulle impostazioni cultura, vedere la sezione dedicata alle impostazioni cultura e alle operazioni asincrone basate sulle attività nell'articolo dedicato alla classe CultureInfo.

Creazione delle continuazioni di attività

I metodi Task.ContinueWith e Task<TResult>.ContinueWith consentono di specificare un'attività da avviare al termine dell'attività precedente. Il delegato dell'attività di continuazione viene passato un riferimento all'attività precedente in modo che possa esaminare lo stato dell'attività precedente. Recuperando il valore della proprietà Task<TResult>.Result, è possibile usare l'output dell'attività precedente come input per la continuazione.

Nell'esempio seguente l'attività getData viene avviata da una chiamata al metodo TaskFactory.StartNew<TResult>(Func<TResult>). L'attività processData viene avviata automaticamente quando termina getData e displayData viene avviata quando termina processData. getData produce una matrice di tipo Integer accessibile da parte dell'attività processData tramite la proprietà getData dell'attività Task<TResult>.Result. L'attività processData elabora la matrice e restituisce un risultato il cui tipo è dedotto dal tipo restituito dell'espressione lambda passata al metodo Task<TResult>.ContinueWith<TNewResult>(Func<Task<TResult>,TNewResult>). L'attività displayData viene eseguita automaticamente quando termina processData e l'oggetto Tuple<T1,T2,T3> restituito dall'espressione lambda processData è accessibile da parte dell'attività displayData tramite la proprietà processData dell'attività Task<TResult>.Result. L'attività displayData accetta il risultato dell'attività processData. Produce un risultato il cui tipo viene dedotto in modo simile e che viene reso disponibile al programma nella proprietà Result.

using System;
using System.Threading.Tasks;

public class ContinuationOne
{
   public static void Main()
   {
      var getData = Task.Factory.StartNew(() => {
                                             Random rnd = new Random();
                                             int[] values = new int[100];
                                             for (int ctr = 0; ctr <= values.GetUpperBound(0); ctr++)
                                                values[ctr] = rnd.Next();

                                             return values;
                                          } );
      var processData = getData.ContinueWith((x) => {
                                                int n = x.Result.Length;
                                                long sum = 0;
                                                double mean;

                                                for (int ctr = 0; ctr <= x.Result.GetUpperBound(0); ctr++)
                                                   sum += x.Result[ctr];

                                                mean = sum / (double) n;
                                                return Tuple.Create(n, sum, mean);
                                             } );
      var displayData = processData.ContinueWith((x) => {
                                                    return String.Format("N={0:N0}, Total = {1:N0}, Mean = {2:N2}",
                                                                         x.Result.Item1, x.Result.Item2,
                                                                         x.Result.Item3);
                                                 } );
      Console.WriteLine(displayData.Result);
   }
}
// The example displays output similar to the following:
//    N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82

Namespace ContinuationsOne
    Module Example
        Public Sub Main()
            Dim getData = Task.Factory.StartNew(Function()
                                                    Dim rnd As New Random()
                                                    Dim values(99) As Integer
                                                    For ctr = 0 To values.GetUpperBound(0)
                                                        values(ctr) = rnd.Next()
                                                    Next
                                                    Return values
                                                End Function)
            Dim processData = getData.ContinueWith(Function(x)
                                                       Dim n As Integer = x.Result.Length
                                                       Dim sum As Long
                                                       Dim mean As Double

                                                       For ctr = 0 To x.Result.GetUpperBound(0)
                                                           sum += x.Result(ctr)
                                                       Next
                                                       mean = sum / n
                                                       Return Tuple.Create(n, sum, mean)
                                                   End Function)
            Dim displayData = processData.ContinueWith(Function(x)
                                                           Return String.Format("N={0:N0}, Total = {1:N0}, Mean = {2:N2}",
                                                                                   x.Result.Item1, x.Result.Item2,
                                                                                   x.Result.Item3)
                                                       End Function)
            Console.WriteLine(displayData.Result)
        End Sub
    End Module
    ' The example displays output like the following:
    '   N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82
End Namespace

Poiché Task.ContinueWith è un metodo di istanza, è possibile concatenare le chiamate al metodo anziché creare un'istanza di un oggetto Task<TResult> per ogni attività precedente. Dal punto di vista funzionale, il seguente esempio è identico al precedente, eccetto per il fatto che unisce in una catena le chiamate al metodo Task.ContinueWith. L'oggetto Task<TResult> restituito dalla catena di chiamate al metodo è l'attività di continuazione finale.

using System;
using System.Threading.Tasks;

public class ContinuationTwo
{
   public static void Main()
   {
      var displayData = Task.Factory.StartNew(() => {
                                                 Random rnd = new Random();
                                                 int[] values = new int[100];
                                                 for (int ctr = 0; ctr <= values.GetUpperBound(0); ctr++)
                                                    values[ctr] = rnd.Next();

                                                 return values;
                                              } ).
                        ContinueWith((x) => {
                                        int n = x.Result.Length;
                                        long sum = 0;
                                        double mean;

                                        for (int ctr = 0; ctr <= x.Result.GetUpperBound(0); ctr++)
                                           sum += x.Result[ctr];

                                        mean = sum / (double) n;
                                        return Tuple.Create(n, sum, mean);
                                     } ).
                        ContinueWith((x) => {
                                        return String.Format("N={0:N0}, Total = {1:N0}, Mean = {2:N2}",
                                                             x.Result.Item1, x.Result.Item2,
                                                             x.Result.Item3);
                                     } );
      Console.WriteLine(displayData.Result);
   }
}
// The example displays output similar to the following:
//    N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82

Namespace ContinuationsTwo
    Module Example
        Public Sub Main()
            Dim displayData = Task.Factory.StartNew(Function()
                                                        Dim rnd As New Random()
                                                        Dim values(99) As Integer
                                                        For ctr = 0 To values.GetUpperBound(0)
                                                            values(ctr) = rnd.Next()
                                                        Next
                                                        Return values
                                                    End Function). _
            ContinueWith(Function(x)
                             Dim n As Integer = x.Result.Length
                             Dim sum As Long
                             Dim mean As Double

                             For ctr = 0 To x.Result.GetUpperBound(0)
                                 sum += x.Result(ctr)
                             Next
                             mean = sum / n
                             Return Tuple.Create(n, sum, mean)
                         End Function). _
            ContinueWith(Function(x)
                             Return String.Format("N={0:N0}, Total = {1:N0}, Mean = {2:N2}",
                                                 x.Result.Item1, x.Result.Item2,
                                                 x.Result.Item3)
                         End Function)
            Console.WriteLine(displayData.Result)
        End Sub
    End Module
    ' The example displays output like the following:
    '   N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82
End Namespace

I metodi ContinueWhenAll e ContinueWhenAny consentono di continuare da più attività.

Per altre informazioni, vedere Concatenamento di attività tramite attività di continuazione.

Creazione di attività figlio scollegate

Quando il codice utente in esecuzione in un'attività crea una nuova attività e non specifica l'opzione AttachedToParent, la nuova attività non viene sincronizzata con l'attività padre in nessun modo particolare. Questo tipo di attività non sincronizzata è definito attività annidata scollegata o attività figlio scollegata. Nell'esempio seguente viene mostrata un'attività che crea un'unica attività figlio scollegata:

var outer = Task.Factory.StartNew(() =>
{
    Console.WriteLine("Outer task beginning.");

    var child = Task.Factory.StartNew(() =>
    {
        Thread.SpinWait(5000000);
        Console.WriteLine("Detached task completed.");
    });
});

outer.Wait();
Console.WriteLine("Outer task completed.");
// The example displays the following output:
//    Outer task beginning.
//    Outer task completed.
//    Detached task completed.
Dim outer = Task.Factory.StartNew(Sub()
                                      Console.WriteLine("Outer task beginning.")
                                      Dim child = Task.Factory.StartNew(Sub()
                                                                            Thread.SpinWait(5000000)
                                                                            Console.WriteLine("Detached task completed.")
                                                                        End Sub)
                                  End Sub)
outer.Wait()
Console.WriteLine("Outer task completed.")
' The example displays the following output:
'     Outer task beginning.
'     Outer task completed.
'    Detached child completed.

Nota

L'attività padre non attende il completamento dell'attività figlio scollegata.

Creazione di attività figlio

Quando il codice utente in esecuzione in un'attività crea un'attività con l'opzione AttachedToParent, la nuova attività è detta attività figlio collegata dell'attività padre. È possibile usare l'opzione AttachedToParent per esprimere il parallelismo fra attività strutturato poiché l'attività padre attende in modo implicito il completamento di tutte le attività figlio collegate. Nell'esempio seguente viene mostrata un'attività padre che crea 10 attività figlio collegate. Nell'esempio viene chiamato il metodo Task.Wait per attendere il completamento dell'attività padre. Non è necessario attendere in modo esplicito il completamento delle attività figlio associate.

using System;
using System.Threading;
using System.Threading.Tasks;

public class Child
{
   public static void Main()
   {
      var parent = Task.Factory.StartNew(() => {
                      Console.WriteLine("Parent task beginning.");
                      for (int ctr = 0; ctr < 10; ctr++) {
                         int taskNo = ctr;
                         Task.Factory.StartNew((x) => {
                                                  Thread.SpinWait(5000000);
                                                  Console.WriteLine("Attached child #{0} completed.",
                                                                    x);
                                               },
                                               taskNo, TaskCreationOptions.AttachedToParent);
                      }
                   });

      parent.Wait();
      Console.WriteLine("Parent task completed.");
   }
}
// The example displays output like the following:
//       Parent task beginning.
//       Attached child #9 completed.
//       Attached child #0 completed.
//       Attached child #8 completed.
//       Attached child #1 completed.
//       Attached child #7 completed.
//       Attached child #2 completed.
//       Attached child #6 completed.
//       Attached child #3 completed.
//       Attached child #5 completed.
//       Attached child #4 completed.
//       Parent task completed.
Imports System.Threading

Namespace Child
    Module Example
        Public Sub Main()
            Dim parent = Task.Factory.StartNew(Sub()
                                                   Console.WriteLine("Parent task beginning.")
                                                   For ctr As Integer = 0 To 9
                                                       Dim taskNo As Integer = ctr
                                                       Task.Factory.StartNew(Sub(x)
                                                                                 Thread.SpinWait(5000000)
                                                                                 Console.WriteLine("Attached child #{0} completed.",
                                                                                                 x)
                                                                             End Sub,
                                                       taskNo, TaskCreationOptions.AttachedToParent)
                                                   Next
                                               End Sub)
            parent.Wait()
            Console.WriteLine("Parent task completed.")
        End Sub
    End Module
    ' The example displays output like the following:
    '       Parent task beginning.
    '       Attached child #9 completed.
    '       Attached child #0 completed.
    '       Attached child #8 completed.
    '       Attached child #1 completed.
    '       Attached child #7 completed.
    '       Attached child #2 completed.
    '       Attached child #6 completed.
    '       Attached child #3 completed.
    '       Attached child #5 completed.
    '       Attached child #4 completed.
    '       Parent task completed.
End Namespace

Un'attività padre può usare l'opzione TaskCreationOptions.DenyChildAttach per impedire che altre attività vengano collegate all'attività padre. Per altre informazioni, vedere Attached and Detached Child Tasks (Attività figlio connesse e disconnesse).

Attesa del completamento delle attività

I tipi System.Threading.Tasks.Task e System.Threading.Tasks.Task<TResult> forniscono vari overload dei metodi Task.Wait che consentono di attendere il completamento di un'attività. Sono inoltre disponibili overload dei metodi statici Task.WaitAll e Task.WaitAny che consentono di attendere il completamento di alcune o di tutte le attività di una matrice.

L'attesa del completamento di un'attività è in genere dovuta a uno dei motivi seguenti:

  • Il thread principale dipende dal risultato finale calcolato da un'attività.

  • È necessario gestire le eccezioni eventualmente generate dall'attività.

  • L'applicazione potrebbe terminare prima del completamento di tutte le attività. Ad esempio, le applicazioni di console termineranno dopo l'esecuzione di tutto il codice sincrono in Main (il punto di ingresso dell'applicazione).

Nell'esempio seguente viene mostrato il modello di base che non prevede la gestione delle eccezioni.

Task[] tasks = new Task[3]
{
    Task.Factory.StartNew(() => MethodA()),
    Task.Factory.StartNew(() => MethodB()),
    Task.Factory.StartNew(() => MethodC())
};

//Block until all tasks complete.
Task.WaitAll(tasks);

// Continue on this thread...
Dim tasks() =
{
    Task.Factory.StartNew(Sub() MethodA()),
    Task.Factory.StartNew(Sub() MethodB()),
    Task.Factory.StartNew(Sub() MethodC())
}

' Block until all tasks complete.
Task.WaitAll(tasks)

' Continue on this thread...

Per un esempio che illustra la gestione delle eccezioni, vedere Gestione delle eccezioni.

Alcuni overload consentono di specificare un timeout mentre altri accettano un oggetto CancellationToken aggiuntivo come parametro di input, in modo che l'attesa stessa possa essere annullata a livello di codice o in risposta a un input dell'utente.

Quando si resta in attesa di un'attività, si attendono in modo implicito tutti i figli di tale attività creati tramite l'opzione TaskCreationOptions.AttachedToParent. L'oggetto Task.Wait restituisce immediatamente un valore se l'attività è già stata completata. Un metodo Task.Wait genererà eventuali eccezioni generate da un'attività, anche se il metodo Task.Wait è stato chiamato dopo il completamento dell'attività.

Composizione di attività

Le classi Task e Task<TResult> forniscono diversi metodi che consentono di comporre più attività. Questi metodi implementano modelli comuni e usano meglio le funzionalità del linguaggio asincrono fornite da C#, Visual Basic e F#. In questa sezione vengono descritti i metodi WhenAll, WhenAny, Delay e FromResult.

Task.WhenAll

Il metodo Task.WhenAll attende in modo asincrono che terminino più oggetti Task o Task<TResult>. Fornisce le versioni di overload che consentono di attendere i set di attività non uniformi. Ad esempio, è possibile attendere il completamento di più oggetti Task e Task<TResult> da una chiamata al metodo.

Task.WhenAny

Il metodo Task.WhenAny attende in modo asincrono che termini uno degli oggetti Task o Task<TResult>. Come nel metodo Task.WhenAll, questo metodo fornisce le versioni di overload che consentono di attendere i set di attività non uniformi. Il metodo WhenAny è particolarmente utile nei seguenti scenari:

  • Operazioni ridondanti: si consideri un algoritmo o un'operazione che può essere eseguita in molti modi. È possibile usare il metodo WhenAny per selezionare l'operazione che termina per prima e quindi annullare le operazioni rimanenti.

  • Operazioni interleaved: è possibile avviare più operazioni che devono terminare e usare il metodo WhenAny per elaborare i risultati al termine di ogni operazione. Al termine di un'operazione, è possibile avviare una o più attività.

  • Operazioni limitate: è possibile usare il metodo WhenAny per estendere lo scenario precedente limitando il numero di operazioni simultanee.

  • Operazioni scadute: è possibile usare il metodo WhenAny per selezionare una o più attività e un'attività che termina dopo un determinato periodo di tempo, ad esempio un'attività restituita dal metodo Delay. Il metodo Delay è descritto nella sezione che segue.

Task.Delay

Il metodo Task.Delay produce un oggetto Task che viene completato allo scadere del tempo specificato. È possibile usare questo metodo per compilare cicli che eseguano il polling dei dati, per specificare timeout, ritardare la gestione dell'input dell'utente e così via.

Task(T).FromResult

Tramite il metodo Task.FromResult, è possibile creare un oggetto Task<TResult> contenente un risultato precedentemente calcolato. Questo metodo è utile quando si esegue un'operazione asincrona che restituisce un oggetto Task<TResult> e il risultato di tale oggetto Task<TResult> è già calcolato. Per un esempio che usa FromResult per recuperare i risultati delle operazioni di download asincrone contenuti in una cache, vedere Procedura: Creare attività precalcolate.

Gestione delle eccezioni nelle attività

Quando un'attività genera una o più eccezioni, il sistema esegue il wrapping di queste ultime in un'eccezione AggregateException. Tale eccezione viene propagata nuovamente al thread che si unisce all'attività. In genere, è il thread in attesa del completamento dell'attività o il thread che accede alla proprietà Result. Questo comportamento impone i criteri di .NET Framework secondo cui tutte le eccezioni non gestite devono comportare per impostazione predefinita la terminazione del processo. Il codice che effettua la chiamata può gestire le eccezioni usando uno qualsiasi degli elementi seguenti in un blocco try/catch:

Anche il thread di unione può gestire le eccezioni. A tale scopo, deve accedere alla proprietà Exception prima che l'attività venga raccolta nel Garbage Collector. L'accesso a questa proprietà impedisce all'eccezione non gestita di attivare il comportamento di propagazione delle eccezioni che termina il processo quando l'oggetto viene finalizzato.

Per ulteriori informazioni sulle eccezioni e sulle attività, vedere Gestione delle eccezioni.

Annullamento delle attività

La classe Task supporta l'annullamento cooperativo ed è completamente integrata con le classi System.Threading.CancellationTokenSource e System.Threading.CancellationToken, che sono state introdotte in .NET Framework 4. Molti dei costruttori nella classe System.Threading.Tasks.Task accettano un oggetto CancellationToken come parametro di input. Molti degli overload StartNew e Run includono anche un parametro CancellationToken.

È possibile creare il token e inviare la richiesta di annullamento in un secondo momento tramite la classe CancellationTokenSource. Passare il token a Task come argomento. Inoltre, fare riferimento allo stesso token nel delegato dell'utente, che esegue le operazioni necessarie per rispondere a una richiesta di annullamento.

Per altre informazioni, vedere Annullamento delle attività e Procedura: Annullare un'attività e i relativi figli.

Classe TaskFactory

La classe TaskFactory fornisce metodi statici che incapsulano alcuni modelli comuni per la creazione e l'avvio di attività e per le attività di continuazione.

L'oggetto TaskFactory predefinito è accessibile come proprietà statica della classe Task o della classe Task<TResult>. È anche possibile creare direttamente un'istanza di TaskFactory e specificare varie opzioni che includono un oggetto CancellationToken, un'opzione TaskCreationOptions, un'opzione TaskContinuationOptions o un oggetto TaskScheduler. Tutte le opzioni specificate quando si crea la factory delle attività verranno applicate a tutte le attività create da tale factory, tranne nel caso in cui l'oggetto Task venga creato tramite l'enumerazione TaskCreationOptions. In tal caso, le opzioni dell'attività eseguono l'override di quelle della factory delle attività.

Attività senza delegati

In alcuni casi sarebbe necessario usare un oggetto Task per incapsulare un'operazione asincrona eseguita da un componente esterno anziché dal delegato dell'utente. Se l'operazione si basa sul modello Begin/End del modello di programmazione asincrona, è possibile usare i metodi FromAsync. In caso contrario, è possibile usare l'oggetto TaskCompletionSource<TResult> per eseguire il wrapping dell'operazione in un'attività e ottenere quindi alcuni dei vantaggi della programmabilità Task. Ad esempio, il supporto per la propagazione e le continuazioni delle eccezioni. Per ulteriori informazioni, vedere TaskCompletionSource<TResult>.

Utilità di pianificazione personalizzate

Per la maggior parte degli sviluppatori di applicazioni o librerie non occorre determinare il processore in cui è in esecuzione l'attività né come quest'ultima sincronizza il proprio lavoro con le altre attività o come viene pianificata in System.Threading.ThreadPool. Tali sviluppatori richiedono soltanto che l'attività venga eseguita con la maggiore efficienza possibile nel computer host. Se si richiede un controllo più accurato sui dettagli di pianificazione, la libreria TPL consente di configurare alcune impostazioni nell'utilità di pianificazione delle attività predefinita. Inoltre, tale libreria consente persino di fornire un'utilità di pianificazione personalizzata. Per ulteriori informazioni, vedere TaskScheduler.

La libreria TPL presenta vari nuovi tipi pubblici che risultano utili negli scenari in parallelo e in quelli sequenziali. Sono incluse diverse classi di raccolta thread-safe, veloci e scalabili nello spazio dei nomi System.Collections.Concurrent e diversi nuovi tipi di sincronizzazione. Ad esempio, System.Threading.Semaphore e System.Threading.ManualResetEventSlim, che sono più efficienti dei relativi predecessori per tipi specifici di carichi di lavoro. Altri nuovi tipi di .NET Framework 4, ad esempio System.Threading.Barrier e System.Threading.SpinLock, forniscono funzionalità non disponibili nelle versioni precedenti. Per ulteriori informazioni, vedere Strutture di dati per la programmazione in parallelo.

Tipi di attività personalizzati

È consigliabile non ereditare da System.Threading.Tasks.Task o System.Threading.Tasks.Task<TResult>. È invece consigliabile usare la proprietà AsyncState per associare i dati o lo stato aggiuntivi a un oggetto Task o Task<TResult>. È anche possibile usare i metodi di estensione per estendere la funzionalità delle classi Task e Task<TResult>. Per altre informazioni sui metodi di estensione, vedere Metodi di estensione e Metodi di estensione.

Se è necessario ereditare da Task o Task<TResult>, non è possibile usare Run o le classi System.Threading.Tasks.TaskFactory, System.Threading.Tasks.TaskFactory<TResult> o System.Threading.Tasks.TaskCompletionSource<TResult> per creare istanze del tipo di attività personalizzato. Non è possibile usarli perché queste classi creano solo oggetti Task e Task<TResult>. Inoltre, non è possibile usare i meccanismi di continuazione delle attività forniti da Task, Task<TResult>, TaskFactory e TaskFactory<TResult> per creare istanze del tipo di attività personalizzato. Non è possibile usarli perché queste classi creano solo oggetti Task e Task<TResult>.

Posizione Descrizione
Concatenamento di attività tramite attività di continuazione Viene descritto il funzionamento delle continuazioni.
Attività figlio connesse e disconnesse Viene descritta la differenza tra attività figlio collegate e scollegate.
Annullamento delle attività Viene descritto il supporto dell'annullamento, incorporato nell'oggetto Task.
Gestione delle eccezioni Viene descritto come vengono gestite le eccezioni su thread simultanei.
Procedura: Usare parallel_invoke per eseguire operazioni in parallelo Viene descritto come usare Invoke.
Procedura: Restituire un valore da un'attività Viene descritto come restituire valori dalle attività.
Procedura: Annullare un'attività e i relativi figli Viene descritto come annullare le attività.
Procedura: Creare attività precalcolate Viene descritto come utilizzare il metodo Task.FromResult per recuperare i risultati delle operazioni di download asincrone contenuti in una cache.
Procedura: Attraversare un albero binario con attività in parallelo Viene descritto come usare le attività per attraversare un albero binario.
Procedura: annullare il wrapping di un'attività annidata Viene illustrato come usare il metodo di estensione Unwrap.
Parallelismo dei dati Viene descritto come utilizzare For e ForEach per creare cicli paralleli su dati.
Programmazione parallela Nodo di livello principale per la programmazione parallela di .NET Framework.

Vedi anche