Condividi tramite


Programmazione asincrona basata su attività

Task Parallel Library (TPL) si basa sul concetto di un'attività , che rappresenta un'operazione asincrona. In alcuni aspetti, un'attività è simile a un thread o a un elemento di lavoro ThreadPool, ma a un livello superiore di astrazione. Il termine parallelismo attività fa riferimento a una o più attività indipendenti in esecuzione simultaneamente. Le attività offrono due vantaggi principali:

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

    Dietro le quinte, le attività vengono accodate al ThreadPool, che è stato migliorato con algoritmi che determinano e regolano 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 per abilitare il parallelismo con granularità fine.

  • Controllo più programmatico di quanto sia possibile con un thread o un elemento di lavoro.

    Le attività e il framework basati su di essi offrono un set completo di API che supportano l'attesa, l'annullamento, le continuazioni, la gestione affidabile delle eccezioni, lo stato dettagliato, la pianificazione personalizzata e altro ancora.

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

Creazione ed esecuzione di attività in modo implicito

Il metodo Parallel.Invoke offre un modo pratico per eseguire contemporaneamente un numero qualsiasi di istruzioni arbitrarie. È sufficiente passare un delegato Action per ogni elemento di lavoro. Il modo più semplice per creare questi delegati consiste nell'usare espressioni lambda. L'espressione lambda può chiamare un metodo denominato o fornire il codice inline. Nell'esempio seguente viene illustrata una chiamata di base Invoke che crea e avvia due attività che vengono eseguite contemporaneamente. La prima attività è rappresentata da un'espressione lambda che chiama un metodo denominato DoSomeWorke 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 di Task create dietro le quinte da Invoke non è necessariamente uguale al numero di delegati forniti. Il TPL potrebbe usare varie ottimizzazioni, in particolare con un numero elevato di delegati.

Per altre informazioni, vedere Procedura: Usare Parallel.Invoke per eseguire operazioni parallele.

Per un maggiore controllo sull'esecuzione dell'attività o sulla restituzione di un valore dall'attività, è necessario usare Task oggetti in modo più esplicito.

Creazione ed esecuzione di attività in modo esplicito

Un'attività che non restituisce un valore è rappresentata dalla classe System.Threading.Tasks.Task. Un'attività che restituisce un valore è 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 alla proprietà Status di un'attività in qualsiasi momento per determinare se è stata avviata l'esecuzione, è stata eseguita fino al completamento, è stata annullata o ha generato un'eccezione. Lo stato è rappresentato da un'enumerazione TaskStatus.

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

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 usano l'utilità di pianificazione predefinita, indipendentemente dall'utilità di pianificazione dell'attività associata al thread corrente. I metodi Run sono il modo preferito per creare e avviare attività quando non è necessario un maggiore controllo sulla creazione e la pianificazione dell'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> ognuno espone una proprietà Factory statica che restituisce un'istanza predefinita di TaskFactory, in modo che sia possibile chiamare il metodo come Task.Factory.StartNew(). Inoltre, nell'esempio seguente, poiché le attività sono di tipo System.Threading.Tasks.Task<TResult>, ognuna ha una proprietà pubblica Task<TResult>.Result che contiene il risultato del calcolo. Le attività vengono eseguite in modo asincrono e possono essere completate in qualsiasi ordine. Se si accede alla proprietà Result prima del completamento del calcolo, 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, è possibile accedere a tutte le variabili visibili in quel punto nel 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é modifica dopo ogni iterazione. Nell'esempio seguente viene illustrato il problema. Passa un contatore del ciclo a un'espressione lambda che istanzia l'oggetto CustomData e utilizza il contatore del ciclo come identificatore dell'oggetto. Come illustrato nell'output dell'esempio, ogni oggetto CustomData ha 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à tramite il relativo costruttore. Nell'esempio seguente viene modificato l'esempio precedente usando il contatore del ciclo durante la creazione dell'oggetto CustomData, che a sua volta viene passato all'espressione lambda. Come mostrato nell'output dell'esempio, ogni oggetto CustomData ha ora un identificatore univoco basato sul valore del contatore del ciclo al momento in cui l'oggetto è stato istanziato.

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à usando 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 intero che lo identifica in modo univoco in un dominio applicazione ed è accessibile usando la proprietà Task.Id. L'ID è utile per visualizzare le informazioni sulle attività nelle finestre "Stack Paralleli" e "Attività" del debugger di Visual Studio e . 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 ulteriori informazioni su come visualizzare gli ID attività nel debugger, vedere Uso della finestra delle attività e Uso della finestra degli stack paralleli.

Le opzioni di creazione delle attività

La maggior parte delle API che creano attività offre sovraccarichi che supportano un parametro TaskCreationOptions. Specificando una o più di queste opzioni, si indica allo scheduler delle attività come programmare l'attività nel pool di thread. Le opzioni possono essere combinate usando un'operazione OR bit per bit.

L'esempio seguente mostra un'attività con le opzioni di 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 locali

Ogni thread ha una cultura e una cultura dell'interfaccia utente associate, definite rispettivamente dalle proprietà Thread.CurrentCulture e Thread.CurrentUICulture. Le impostazioni culturali di un thread vengono utilizzate in operazioni come la formattazione, l'analisi, l'ordinamento e il confronto delle stringhe. La cultura dell'interfaccia utente di un thread viene utilizzata per la ricerca delle risorse.

Le impostazioni culturali del sistema definiscono la cultura predefinita e quella 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 imposta esplicitamente la cultura di un thread e si avvia un nuovo thread, il nuovo thread non eredita la cultura del thread chiamante; invece, la sua cultura è quella predefinita del sistema. Tuttavia, nella programmazione basata su task, i task utilizzano la cultura del thread chiamante, anche se il task viene eseguito in modo asincrono su un thread diverso.

Nell'esempio seguente viene fornita una semplice illustrazione. Cambia la cultura corrente dell'app in francese (Francia). Se la lingua corrente è già impostata su francese (Francia), viene cambiata in inglese (degli Stati Uniti). Richiama quindi un delegato denominato formatDelegate che restituisce alcuni numeri formattati come valore monetario nel nuovo contesto culturale. Se il delegato viene invocato da un task in modo sincrono o asincrono, il task utilizza le impostazioni culturali 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, la cultura di un task è determinata dalla cultura del thread su cui viene eseguita, non dalla cultura del thread che chiama. Per le attività asincrone, la cultura utilizzata dall'attività potrebbe essere diversa dalla cultura del thread chiamante.

Per ulteriori informazioni sulle attività asincrone e sulla cultura, vedere la sezione "Cultura e operazioni basate su attività asincrone" nell'articolo CultureInfo.

Creazione di 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'antecedente 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 al termine della getData e displayData viene avviata al termine dell'processData. getData produce una matrice integer, accessibile all'attività processData tramite la proprietà Task<TResult>.Result dell'attività getData. L'attività processData elabora la matrice e restituisce un risultato il cui tipo viene dedotto dal tipo restituito dell'espressione lambda passata al metodo Task<TResult>.ContinueWith<TNewResult>(Func<Task<TResult>,TNewResult>). L'attività displayData viene eseguita automaticamente al termine dell'processData e l'oggetto Tuple<T1,T2,T3> restituito dall'espressione lambda processData è accessibile all'attività displayData tramite la proprietà Task<TResult>.Result dell'attività processData. L'attività displayData ottiene 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 invocazioni dei metodi invece di creare un'istanza di un oggetto Task<TResult> per ogni attività precedente. L'esempio seguente è funzionalmente identico a quello precedente, ad eccezione del fatto che concatena le chiamate al metodo Task.ContinueWith. L'oggetto Task<TResult> restituito dalle chiamate di metodo concatenate è 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 proseguire da diverse attività.

Per altre informazioni, vedere concatenamento delle 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 modo speciale. Questo tipo di attività non sincronizzata viene chiamata attività nidificata scollegata o attività figlia scollegata. L'esempio seguente mostra un'attività che crea un'attività figlia distaccata.

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à figlie

Quando il codice utente in esecuzione in un'attività crea un'attività con l'opzione AttachedToParent, la nuova attività è nota come attività figlio associato dell'attività principale. È possibile usare l'opzione AttachedToParent per esprimere il parallelismo delle attività strutturate perché l'attività padre attende implicitamente la conclusione di tutte le attività figlie associate. L'esempio seguente mostra un'attività padre che crea 10 attività figlio associate. Nell'esempio viene chiamato il metodo Task.Wait per aspettare che l'attività padre finisca. Non è necessario attendere esplicitamente il completamento delle attività figlie 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 l'associazione di altre attività all'attività padre. Per altre informazioni, vedere attività figlio collegate e scollegate.

In attesa del completamento delle attività

I tipi System.Threading.Tasks.Task e System.Threading.Tasks.Task<TResult> forniscono diversi overload dei metodi Task.Wait che consentono di attendere il completamento di un'attività. Inoltre, gli overload dei metodi statici Task.WaitAll e Task.WaitAny consentono di attendere il completamento di una o più di una matrice di attività.

In genere, è necessario attendere un'attività per uno dei motivi seguenti:

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

  • È necessario gestire le eccezioni che potrebbero essere generate dall'attività.

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

L'esempio seguente illustra il modello di base che non comporta 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 mostra la gestione delle eccezioni, vedere gestione delle eccezioni.

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

Quando si attende un'attività, si attende in modo implicito tutti gli elementi figlio di tale attività creati usando l'opzione TaskCreationOptions.AttachedToParent. Task.Wait restituisce immediatamente 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 delle 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, Delaye FromResult .

Task.WhenAll

Il metodo Task.WhenAll attende in modo asincrono il completamento di oggetti Task o Task<TResult> multipli. Fornisce versioni sovraccarico che consentono di attendere insiemi di attività non uniformi. Ad esempio, è possibile attendere il completamento di oggetti Task e Task<TResult> da una sola chiamata al metodo.

Task.WhenAny

Il metodo Task.WhenAny attende in modo asincrono il completamento di uno di più oggetti Task o Task<TResult>. Come nel metodo Task.WhenAll, questo metodo fornisce versioni sovraccaricate che consentono di attendere i set di attività non uniformi. Il metodo WhenAny è particolarmente utile negli scenari seguenti:

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

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

  • operazioni rallentate: è 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 tra una o più attività e un'attività che termina dopo un'ora specifica, ad esempio un'attività restituita dal metodo Delay. Il metodo Delay è descritto nella sezione seguente.

Task.Delay

Il metodo Task.Delay produce un oggetto Task che termina dopo l'ora specificata. È possibile utilizzare questo metodo per creare cicli che eseguano il polling dei dati, specificare timeout, ritardare la gestione dell'input dell'utente e così via.

Task(T).FromResult

Usando il metodo Task.FromResult, è possibile creare un oggetto Task<TResult> che contiene un risultato precalcorato. 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 contenute in una cache, vedere procedura : Creare attività pre-calcolate.

Gestione delle eccezioni nelle attività

Quando un'attività genera una o più eccezioni, le eccezioni vengono incluse 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 del thread che accede alla proprietà Result. Questo comportamento applica i criteri di .NET Framework che tutte le eccezioni non gestite per impostazione predefinita devono terminare il processo. Il codice chiamante può gestire le eccezioni usando uno degli elementi seguenti in un blocco try/catch:

Il thread di unione può anche gestire le eccezioni accedendo alla proprietà Exception prima che l'attività venga sottoposta a raccolta dei rifiuti. Accedendo a questa proprietà, si impedisce all'eccezione non gestita di attivare il comportamento di propagazione delle eccezioni che termina il processo quando l'oggetto viene finalizzato.

Per altre 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 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 ed eseguire la richiesta di annullamento in un secondo momento usando la classe CancellationTokenSource. Trasmetti il token al Task come argomento e fai riferimento allo stesso token nel delegato dell'utente, che si occupa di rispondere a una richiesta di annullamento.

Per ulteriori informazioni, vedere Annullamento delle attività e Procedura: Annullare un'attività e i relativi elementi figlio .

Classe TaskFactory

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

È possibile accedere al TaskFactory predefinito come proprietà statica nella classe Task o Task<TResult>. È anche possibile creare un'istanza di un TaskFactory direttamente e specificare varie opzioni che includono un'opzione CancellationToken, un'opzione TaskCreationOptions, un'opzione TaskContinuationOptions o un'opzione TaskScheduler. Tutte le opzioni specificate quando si crea la fabbrica di attività verranno applicate a tutte le attività create a meno che il Task non venga creato usando l'enumerazione TaskCreationOptions, nel qual caso le opzioni dell'attività prioritizzano quelle della fabbrica di attività.

Attività senza delegati

In alcuni casi, è possibile usare un Task per incapsulare un'operazione asincrona eseguita da un componente esterno anziché dal delegato dell'utente. Se l'operazione è basata sul modello di inizio/fine del modello di programmazione asincrona, è possibile usare i metodi FromAsync. In caso contrario, è possibile usare l'oggetto TaskCompletionSource<TResult> per incapsulare l'operazione in un'attività e ottenere così alcuni vantaggi della programmabilità Task. Ad esempio, il supporto per la propagazione e le continuazioni delle eccezioni. Per altre informazioni, vedere TaskCompletionSource<TResult>.

Pianificatori personalizzati

La maggior parte degli sviluppatori di applicazioni o librerie non è occupata dal processore in cui viene eseguita l'attività, dal modo in cui sincronizza il lavoro con altre attività o dal modo in cui è pianificata nel System.Threading.ThreadPool. Richiedono solo l'esecuzione il più efficiente possibile nel computer host. Se è necessario un controllo più dettagliato sui dettagli di pianificazione, il TPL consente di configurare alcune impostazioni nell'utilità di pianificazione predefinita e persino di fornire un'utilità di pianificazione personalizzata. Per altre informazioni, vedere TaskScheduler.

Il TPL include diversi nuovi tipi pubblici utili in scenari paralleli e 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 predecessori per tipi specifici di carichi di lavoro. Altri nuovi tipi in .NET Framework 4, ad esempio, System.Threading.Barrier e System.Threading.SpinLock, forniscono funzionalità non disponibili nelle versioni precedenti. Per altre informazioni, vedere Strutture di dati per la programmazione parallela.

Tipi di attività personalizzati

È consigliabile non ereditare da System.Threading.Tasks.Task o System.Threading.Tasks.Task<TResult>. È invece consigliabile utilizzare la proprietà AsyncState per associare dati o stati aggiuntivi a un oggetto Task o Task<TResult>. È anche possibile usare 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 le classi Run o 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 Task e Task<TResult> oggetti. Inoltre, non è possibile usare i meccanismi di continuazione delle attività forniti da Task, Task<TResult>, TaskFactorye TaskFactory<TResult> per creare istanze del tipo di attività personalizzato. Non è possibile usarle perché anche queste classi creano solo Task e oggetti Task<TResult>.

Titolo Descrizione
concatenamento delle attività tramite attività di continuazione Descrive il funzionamento delle continuazioni.
attività figlio collegate e scollegate Descrive la differenza tra le attività figlie collegate e scollegate.
Annullamento attività Descrive il supporto per l'annullamento integrato nell'oggetto Task.
gestione delle eccezioni Viene descritto il modo in cui vengono gestite le eccezioni nei thread simultanei.
Procedura: Usare Parallel.Invoke per eseguire operazioni parallele 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 elementi figlio Descrive come annullare le attività.
Procedura: Creare attività pre-computate Viene descritto come utilizzare il metodo Task.FromResult per recuperare i risultati delle operazioni di download asincrone contenute in una cache.
Procedura: Attraversare un albero binario con attività parallele Viene descritto come usare le attività per attraversare un albero binario.
Procedura: Svolgere un'attività annidata Illustra come usare il metodo di estensione Unwrap.
parallelismo dei dati Viene descritto come usare For e ForEach per creare cicli paralleli sui dati.
programmazione parallela Nodo di primo livello per la programmazione parallela di .NET Framework.

Vedere anche