Programowanie asynchroniczne oparte na zadaniach

Biblioteka równoległa zadań (TPL) opiera się na koncepcji zadania, które reprezentuje operację asynchroniczną. W pewnym sensie zadanie przypomina wątek lub ThreadPool element roboczy, ale na wyższym poziomie abstrakcji. Termin równoległość zadań odnosi się do co najmniej jednego niezależnego zadania uruchomionego współbieżnie. Zadania zapewniają dwie podstawowe korzyści:

  • Efektywniejsze i bardziej skalowalne wykorzystanie zasobów systemowych.

    W tle zadania są ustawiane w kolejce do ThreadPoolobiektu , który został ulepszony za pomocą algorytmów określających i dostosowujących się do liczby wątków. Te algorytmy zapewniają równoważenie obciążenia w celu zmaksymalizowania przepływności. Ten proces sprawia, że zadania są stosunkowo lekkie i można utworzyć wiele z nich w celu umożliwienia precyzyjnego równoległości.

  • Większa kontrola programistyczna niż jest to możliwe za pomocą wątku lub elementu roboczego.

    Zadania i środowisko zbudowane wokół nich zawierają bogaty zestaw interfejsów API, które obsługują oczekiwanie, anulowanie, kontynuację, niezawodną obsługę wyjątków, szczegółowe informacje o stanie, planowanie niestandardowe i nie tylko.

Z obu powodów TPL jest preferowanym interfejsem API do pisania wielowątkowego, asynchronicznego i równoległego kodu na platformie .NET.

Tworzenie i uruchamianie zadań niejawnie

Metoda Parallel.Invoke zapewnia wygodny sposób uruchamiania dowolnej liczby dowolnych instrukcji jednocześnie. Wystarczy przekazać delegata Action dla każdego elementu pracy. Wyrażenia lambda są najwygodniejszym sposobem na tworzenie tych delegatów. Wyrażenie lambda może wywołać metodę nazwaną lub zapewnić kod inline. W poniższym przykładzie przedstawiono podstawowe Invoke wywołanie, które tworzy i uruchamia dwa zadania uruchamiane współbieżnie. Pierwsze zadanie jest reprezentowane przez wyrażenie lambda, które wywołuje metodę o nazwie DoSomeWork, a drugie zadanie jest reprezentowane przez wyrażenie lambda, które wywołuje metodę o nazwie DoSomeOtherWork.

Uwaga

Ta dokumentacja używa wyrażeń lambda do definiowania delegatów w TPL. Jeśli nie znasz wyrażeń lambda w języku C# lub Visual Basic, zobacz Wyrażenia lambda w plINQ i TPL.

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

Uwaga

Liczba Task wystąpień tworzonych za kulisami Invoke nie musi być równa liczbie dostarczonych delegatów. TPL może stosować różne optymalizacje, szczególnie w przypadku dużej liczby delegatów.

Aby uzyskać więcej informacji, zobacz How to: Use Parallel.Invoke to Execute Parallel Operations (Instrukcje: używanie metody Parallel.Invoke do wykonywania operacji równoległych).

Aby uzyskać większą kontrolę nad wykonywaniem zadania lub zwrócić wartość z zadania, należy pracować z obiektami Task bardziej jawnie.

Jawne tworzenie i uruchamianie zadań

Zadanie, które nie zwraca wartości, jest reprezentowane przez klasę System.Threading.Tasks.Task . Zadanie zwracające wartość jest reprezentowane przez System.Threading.Tasks.Task<TResult> klasę, która dziedziczy z Taskklasy . Obiekt zadania obsługuje szczegóły infrastruktury i zapewnia metody i właściwości, które są dostępne z wątku wywoływania przez cały okres istnienia zadania. Na przykład można uzyskać dostęp do Status właściwości zadania w dowolnym momencie, aby określić, czy uruchomiono zadanie, uruchomiono do ukończenia, zostało anulowane lub zgłosiło wyjątek. Stan jest reprezentowany przez TaskStatus wyliczenie.

Tworząc zadanie, nadajesz mu delegata użytkownika hermetyzującego kod, który zadanie będzie wykonywało. Delegat może być wyrażony jako delegat nazwany, metoda anonimowa lub wyrażenie lambda. Wyrażenia lambda mogą zawierać wywołanie metody nazwanej, jak pokazano w następującym przykładzie. W przykładzie przedstawiono wywołanie Task.Wait metody , aby upewnić się, że zadanie zakończy wykonywanie przed zakończeniem działania aplikacji trybu konsoli.

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 like the following:
//       Hello from thread 'Main'.
//       Hello from taskA.
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

Za pomocą Task.Run metod można również utworzyć i uruchomić zadanie w jednej operacji. Aby zarządzać zadaniem, Run metody używają domyślnego harmonogramu zadań, niezależnie od tego, który harmonogram zadań jest skojarzony z bieżącym wątkiem. Metody Run są preferowanym sposobem tworzenia i uruchamiania zadań, gdy nie jest potrzebna większa kontrola nad tworzeniem i planowaniem zadania.

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 like the following:
//       Hello from thread 'Main'.
//       Hello from taskA.
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

Możesz również użyć TaskFactory.StartNew metody , aby utworzyć i uruchomić zadanie w jednej operacji. Jak pokazano w poniższym przykładzie, można użyć tej metody, gdy:

  • Tworzenie i planowanie nie musi być oddzielone i wymagane są dodatkowe opcje tworzenia zadań lub użycie określonego harmonogramu.

  • Musisz przekazać dodatkowy stan do zadania, które można pobrać za pośrednictwem jego Task.AsyncState właściwości.

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 i Task<TResult> każdy uwidacznia właściwość statyczną Factory zwracającą domyślne wystąpienie klasy TaskFactory, aby można było wywołać metodę jako Task.Factory.StartNew(). Ponadto w poniższym przykładzie, ponieważ zadania są typu System.Threading.Tasks.Task<TResult>, każdy z nich ma właściwość publiczną Task<TResult>.Result , która zawiera wynik obliczeń. Zadania są uruchamiane asynchronicznie i mogą zostać wykonane w dowolnej kolejności. Result Jeśli właściwość zostanie udostępniona przed zakończeniem obliczeń, właściwość blokuje wątek wywołujący do momentu udostępnienia wartości.

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

Aby uzyskać więcej informacji, zobacz Instrukcje: zwracanie wartości z zadania.

Używając wyrażenia lambda do utworzenia delegata, masz dostęp do wszystkich zmiennych, które są widoczne w tym momencie w kodzie źródłowym. Jednak w niektórych przypadkach, zwłaszcza w pętlach, lambda nie przechwytuje zmiennej zgodnie z oczekiwaniami. Przechwytuje tylko odwołanie do zmiennej, a nie wartość, ponieważ jest mutates po każdej iteracji. Poniższy przykład ilustruje ten problem. Przekazuje licznik pętli do wyrażenia lambda, które tworzy wystąpienie CustomData obiektu i używa licznika pętli jako identyfikatora obiektu. Jak pokazano w danych wyjściowych z przykładu, każdy CustomData obiekt ma identyczny identyfikator.

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

Możesz uzyskać dostęp do wartości w każdej iteracji poprzez zapewnienie obiektu stanu do zadania za pośrednictwem jej konstruktora. Poniższy przykład modyfikuje poprzedni przykład przy użyciu licznika pętli podczas tworzenia CustomData obiektu, który z kolei jest przekazywany do wyrażenia lambda. Jak pokazuje dane wyjściowe z przykładu, każdy CustomData obiekt ma teraz unikatowy identyfikator na podstawie wartości licznika pętli w momencie utworzenia wystąpienia obiektu.

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

Ten stan jest przekazywany jako argument do delegata zadania i można uzyskać do niego dostęp z obiektu zadania przy użyciu Task.AsyncState właściwości . Poniższy przykład jest modyfikacją poprzedniego. Używa AsyncState właściwości do wyświetlania informacji o CustomData obiektach przekazanych do wyrażenia 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

Identyfikator zadania

Każde zadanie otrzymuje identyfikator liczby całkowitej, który jednoznacznie identyfikuje go w domenie aplikacji i można uzyskać do niego dostęp przy użyciu Task.Id właściwości . Identyfikator jest przydatny do wyświetlania informacji o zadaniu w oknach Równoległe stosy i zadania debugera programu Visual Studio. Identyfikator jest tworzony leniwie, co oznacza, że nie jest tworzony, dopóki nie zostanie wyświetlony żądanie. W związku z tym zadanie może mieć inny identyfikator za każdym razem, gdy program jest uruchamiany. Aby uzyskać więcej informacji na temat wyświetlania identyfikatorów zadań w debugerze, zobacz Using the Tasks Window (Korzystanie z okna Zadania ) i Using the Parallel Stacks Window (Używanie okna stosów równoległych).

Opcje tworzenia zadań

Większość interfejsów API tworzących zadania zapewnia przeciążenia, które akceptują TaskCreationOptions parametr. Określając co najmniej jedną z tych opcji, należy poinformować harmonogram zadań, jak zaplanować zadanie w puli wątków. Opcje mogą być łączone za pomocą bitowej operacji OR .

W poniższym przykładzie przedstawiono zadanie z LongRunning opcjami i 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()

Zadania, wątki i kultura

Każdy wątek ma powiązaną kulturę i kulturę interfejsu użytkownika, która jest definiowana odpowiednio przez Thread.CurrentCulture właściwości i Thread.CurrentUICulture . Kultura wątku jest używana w operacjach, takich jak formatowanie, analizowanie, sortowanie i operacje porównywania ciągów. Kultura interfejsu użytkownika wątku jest używana w wyszukiwaniu zasobów.

Kultura systemowa definiuje kulturę domyślną i kulturę interfejsu użytkownika wątku. Można jednak określić kulturę domyślną dla wszystkich wątków w domenie aplikacji przy użyciu CultureInfo.DefaultThreadCurrentCulture właściwości i CultureInfo.DefaultThreadCurrentUICulture . Jeśli jawnie ustawisz kulturę wątku i uruchomisz nowy wątek, nowy wątek nie dziedziczy kultury wątku wywołującego; Zamiast tego jego kultura jest domyślną kulturą systemową. Jednak w programowaniu opartym na zadaniach zadania zadania używają kultury wywołującego wątku, nawet jeśli zadanie jest uruchamiane asynchronicznie w innym wątku.

Poniższy przykład zawiera prostą ilustrację. Zmienia bieżącą kulturę aplikacji na francuski (Francja). Jeśli francuski (Francja) jest już obecną kulturą, zmienia się na angielski (Stany Zjednoczone). Następnie wywołuje delegata o nazwie formatDelegate , który zwraca niektóre liczby sformatowane jako wartości waluty w nowej kulturze. Niezależnie od tego, czy delegat jest wywoływany przez zadanie synchronicznie, czy asynchronicznie, zadanie używa kultury wywołującego wątku.

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 €

Uwaga

W wersjach .NET Framework starszych niż .NET Framework 4.6 kultura zadania jest określana przez kulturę wątku, na którym działa, a nie kulturę wątku wywołującego. W przypadku zadań asynchronicznych kultura używana przez zadanie może różnić się od kultury wątku wywołującego.

Aby uzyskać więcej informacji na temat zadań asynchronicznych i kultury, zobacz sekcję "Culture and asynchronous task-based operations" (Operacje asynchroniczne i asynchroniczne) w CultureInfo artykule.

Tworzenie kontynuacji zadań

Metody Task.ContinueWith i Task<TResult>.ContinueWith umożliwiają określenie zadania, które ma być uruchamiane po zakończeniu zadania przedzibowego . Delegat zadania kontynuacji jest przekazywany odwołanie do zadania wcześniejszego, aby można było zbadać stan zadania poprzednika. A po pobraniu wartości Task<TResult>.Result właściwości można użyć danych wyjściowych poprzednika jako danych wejściowych dla kontynuacji.

W poniższym przykładzie getData zadanie jest uruchamiane przez wywołanie TaskFactory.StartNew<TResult>(Func<TResult>) metody . Zadanie processData jest uruchamiane automatycznie po getData zakończeniu i displayData jest uruchamiane po processData zakończeniu. getData tworzy tablicę całkowitą, która jest dostępna dla processData zadania za pomocą getData właściwości zadania Task<TResult>.Result . processData Zadanie przetwarza tablicę i zwraca wynik, którego typ jest wnioskowany z zwracanego typu wyrażenia lambda przekazanego Task<TResult>.ContinueWith<TNewResult>(Func<Task<TResult>,TNewResult>) do metody. Zadanie displayData jest wykonywane automatycznie po processData zakończeniu, a Tuple<T1,T2,T3> obiekt zwrócony przez processData wyrażenie lambda jest dostępny dla displayData zadania za pośrednictwem processData właściwości zadania Task<TResult>.Result . Zadanie displayData przyjmuje wynik processData zadania. Tworzy wynik, którego typ jest wywnioskowany w podobny sposób, i który jest udostępniany programowi Result we właściwości.

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

Ponieważ Task.ContinueWith jest to metoda wystąpienia, można połączyć wywołania metody ze sobą zamiast utworzyć Task<TResult> wystąpienie obiektu dla każdego zadania przedzidentowego. Poniższy przykład jest funkcjonalnie identyczny z poprzednim, z tą różnicą, że łączy ze sobą wywołania metody Task.ContinueWith . Obiekt Task<TResult> zwracany przez łańcuch wywołań metody jest ostatnim zadaniem kontynuacji.

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

Metody ContinueWhenAll i ContinueWhenAny umożliwiają kontynuowanie pracy z wielu zadań.

Aby uzyskać więcej informacji, zobacz Łączenie zadań za pomocą zadań kontynuacji.

Tworzenie odłączonych zadań podrzędnych

Gdy kod użytkownika uruchomiony w zadaniu tworzy nowe zadanie i nie określa AttachedToParent opcji, nowe zadanie nie jest synchronizowane z zadaniem nadrzędnym w żaden specjalny sposób. Ten typ niezsynchronizowanego zadania jest nazywany zadaniem zagnieżdżonym odłączonym lub odłączonym podrzędnym. W poniższym przykładzie pokazano zadanie, które tworzy jedno odłączone zadanie podrzędne:

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.

Uwaga

Zadanie nadrzędne nie czeka na zakończenie odłączonego zadania podrzędnego.

Tworzenie zadań podrzędnych

Gdy kod użytkownika uruchomiony w zadaniu tworzy zadanie z opcją AttachedToParent , nowe zadanie jest nazywane dołączonym podrzędnym zadaniem zadania nadrzędnego. Możesz użyć AttachedToParent opcji , aby wyrazić równoległość zadań strukturalnych, ponieważ zadanie nadrzędne niejawnie czeka na zakończenie wszystkich dołączonych zadań podrzędnych. W poniższym przykładzie pokazano zadanie nadrzędne, które tworzy 10 dołączonych zadań podrzędnych. W przykładzie metoda wywołuje metodę Task.Wait oczekiwania na zakończenie zadania nadrzędnego. Nie musi jawnie czekać na ukończenie dołączonych zadań podrzędnych.

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

Zadanie nadrzędne może użyć TaskCreationOptions.DenyChildAttach opcji , aby uniemożliwić dołączanie innych zadań do zadania nadrzędnego. Aby uzyskać więcej informacji, zobacz Dołączone i odłączone zadania podrzędne.

Oczekiwanie na zakończenie zadań

Typy System.Threading.Tasks.Task i System.Threading.Tasks.Task<TResult> zapewniają kilka przeciążeń Task.Wait metod, które umożliwiają oczekiwanie na zakończenie zadania. Ponadto przeciążenia metod statycznych Task.WaitAll i Task.WaitAny umożliwiają oczekiwanie na zakończenie dowolnej lub całej tablicy zadań podrzędnych.

Zazwyczaj użytkownik czeka na zadanie z jednego z poniższych powodów:

  • Główny wątek zależy od wyniku końcowego obliczonego przez zadanie.

  • Trzeba obsługiwać wyjątki, które mogą być zgłoszone przez zadanie.

  • Aplikacja może zakończyć działanie przed zakończeniem wykonywania wszystkich zadań. Na przykład aplikacje konsolowe zakończą działanie po wykonaniu wszystkich synchronicznych kodu w Main (punkcie wejścia aplikacji).

W poniższym przykładzie przedstawiono podstawowy wzorzec, który nie obejmuje obsługi wyjątków:

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...

Aby zapoznać się z przykładem obsługi wyjątków, zobacz Obsługa wyjątków.

Niektóre przeciążenia umożliwiają określenie limitu czasu, a inne przyjmują dodatkowy CancellationToken parametr wejściowy, aby można było anulować samo oczekiwanie programowo lub w odpowiedzi na dane wejściowe użytkownika.

Gdy czekasz na zadanie, niejawnie czekasz na wszystkie elementy podrzędne tego zadania, które zostały utworzone przy użyciu TaskCreationOptions.AttachedToParent opcji . Task.Wait metoda zwraca natychmiast, jeśli zadanie zostało już ukończone. Metoda Task.Wait zgłosi wszelkie wyjątki zgłoszone przez zadanie, nawet jeśli Task.Wait metoda została wywołana po zakończeniu zadania.

Tworzenie zadań

Klasy Task i Task<TResult> udostępniają kilka metod, które ułatwiają tworzenie wielu zadań. Te metody implementują typowe wzorce i lepiej wykorzystują funkcje języka asynchronicznego, które są udostępniane przez języki C#, Visual Basic i F#. W tej sekcji opisano WhenAllmetody , WhenAny, Delayi FromResult .

Task.WhenAll

Metoda Task.WhenAll asynchronicznie czeka na zakończenie wielu Task obiektów lub Task<TResult> . Zapewnia przeciążone wersje, które umożliwiają czekanie na niejednolite zestawy zadań. Na przykład można poczekać na ukończenie wielu Task obiektów i Task<TResult> z jednego wywołania metody.

Task.WhenAny

Metoda Task.WhenAny asynchronicznie czeka na zakończenie jednego z wielu Task obiektów lub Task<TResult> . Podobnie jak w metodzie Task.WhenAll ta metoda zapewnia przeciążone wersje, które umożliwiają oczekiwanie na nieumundurowane zestawy zadań. Metoda jest szczególnie przydatna WhenAny w następujących scenariuszach:

  • Nadmiarowe operacje: rozważ użycie algorytmu lub operacji, które można wykonać na wiele sposobów. Możesz użyć WhenAny metody , aby wybrać operację, która zakończy się najpierw, a następnie anulować pozostałe operacje.

  • Operacje przeplotowe: można uruchomić wiele operacji, które muszą zakończyć działanie i użyć metody do przetwarzania wyników po zakończeniu WhenAny każdej operacji. Po zakończeniu jednej operacji można uruchomić jedno lub więcej zadań.

  • Operacje z ograniczeniami: możesz użyć WhenAny metody w celu rozszerzenia poprzedniego scenariusza przez ograniczenie liczby operacji współbieżnych.

  • Wygasłe operacje: możesz użyć WhenAny metody , aby wybrać między jednym lub większą liczbą zadań a zadaniem kończącym się po określonym czasie, takim jak zadanie zwrócone przez metodę Delay . Metoda Delay została opisana w poniższej sekcji.

Task.Delay

Metoda Task.Delay tworzy Task obiekt, który kończy się po upływie określonego czasu. Za pomocą tej metody można tworzyć pętle sondujące dane, aby określić limity czasu, opóźnić obsługę danych wejściowych użytkownika itd.

Task(T).FromResult

Za pomocą Task.FromResult metody można utworzyć Task<TResult> obiekt, który zawiera wstępnie obliczony wynik. Ta metoda jest przydatna podczas wykonywania operacji asynchronicznej, która zwraca Task<TResult> obiekt, a wynik tego Task<TResult> obiektu jest już obliczany. Aby zapoznać się z przykładem używanym FromResult do pobierania wyników asynchronicznych operacji pobierania przechowywanych w pamięci podręcznej, zobacz Instrukcje: tworzenie wstępnie obliczonych zadań.

Obsługa wyjątków w zadaniach

Gdy zadanie zgłasza jeden lub więcej wyjątków, wyjątki są opakowane w AggregateException wyjątek. Ten wyjątek jest propagowany z powrotem do wątku, który łączy się z zadaniem. Zazwyczaj jest to wątek czekający na zakończenie zadania lub wątek, który uzyskuje Result dostęp do właściwości. To zachowanie wymusza zasady .NET Framework, że wszystkie nieobsługiwane wyjątki domyślnie powinny zakończyć proces. Kod wywołujący może obsługiwać wyjątki przy użyciu dowolnego z następujących elementów w try/catch bloku:

Wątek sprzęgania może również obsługiwać wyjątki, korzystając Exception z właściwości przed usunięciem pamięci przez zadanie. Dostęp do tej właściwości zapobiega temu, że nieobsługiwany wyjątek inicjuje zachowanie propagacji wyjątku, które przerywa proces wraz z finalizacją obiektu.

Aby uzyskać więcej informacji na temat wyjątków i zadań, zobacz Obsługa wyjątków.

Anulowanie zadań

Klasa Task obsługuje kooperatywne anulowanie i jest w pełni zintegrowana z klasami System.Threading.CancellationTokenSource iSystem.Threading.CancellationToken, które zostały wprowadzone w .NET Framework 4. Wiele konstruktorów w System.Threading.Tasks.Task klasie przyjmuje CancellationToken obiekt jako parametr wejściowy. StartNew Wiele przeciążeń i Run zawiera CancellationToken również parametr .

Token można utworzyć i wysłać żądanie anulowania w późniejszym czasie przy użyciu CancellationTokenSource klasy . Przekaż token jako argument, a także odwołaj się do Task tego samego tokenu w delegatu użytkownika, co działa w odpowiedzi na żądanie anulowania.

Aby uzyskać więcej informacji, zobacz Anulowanie zadania i Instrukcje: Anulowanie zadania i jego elementów podrzędnych.

Klasa TaskFactory

Klasa TaskFactory udostępnia metody statyczne, które hermetyzują typowe wzorce tworzenia i uruchamiania zadań oraz zadań kontynuacji.

Dostęp do wartości domyślnej TaskFactory można uzyskać jako właściwość statyczną w Task klasie lub Task<TResult> klasie. Można również utworzyć TaskFactory wystąpienie bezpośrednio i określić różne opcje, które obejmują CancellationToken, TaskCreationOptions opcję, TaskContinuationOptions opcję lub TaskScheduler. Niezależnie od opcji określonych podczas tworzenia fabryki zadań zostaną zastosowane do wszystkich tworzonych zadań, chyba że Task element zostanie utworzony przy użyciu TaskCreationOptions wyliczenia, w takim przypadku opcje zadania zastępują te z fabryki zadań.

Zadania bez delegatów

W niektórych przypadkach można użyć elementu Task , aby hermetyzować niektóre operacje asynchroniczne wykonywane przez składnik zewnętrzny zamiast delegata użytkownika. Jeśli operacja jest oparta na wzorcu Początek/koniec modelu programowania asynchronicznego, możesz użyć FromAsync metod . Jeśli tak nie jest, możesz użyć TaskCompletionSource<TResult> obiektu do opakowania operacji w zadaniu, a tym samym uzyskać niektóre korzyści z Task programowania. Na przykład obsługa propagacji wyjątków i kontynuacji. Aby uzyskać więcej informacji, zobacz TaskCompletionSource<TResult>.

Niestandardowe harmonogramy

Większość deweloperów aplikacji lub bibliotek nie obchodzi, na którym procesorze jest uruchamiane zadanie, jak synchronizuje swoją pracę z innymi zadaniami lub jak jest on zaplanowany na System.Threading.ThreadPool. Wymagają one tylko, aby było wykonane jak najwydajniej na komputerze-hoście. Jeśli potrzebujesz bardziej szczegółowej kontroli nad szczegółami planowania, TPL umożliwia skonfigurowanie niektórych ustawień w domyślnym harmonogramie zadań, a nawet umożliwia podanie niestandardowego harmonogramu. Aby uzyskać więcej informacji, zobacz TaskScheduler.

TPL ma kilka nowych typów publicznych, które są przydatne równolegle i sekwencyjne scenariusze. Obejmują one kilka klas kolekcji bezpiecznych wątkowo, szybkich i skalowalnych w System.Collections.Concurrent przestrzeni nazw oraz kilka nowych typów synchronizacji. Na przykład System.Threading.Semaphore i System.Threading.ManualResetEventSlim, które są bardziej wydajne niż ich poprzedniki dla konkretnych rodzajów obciążeń. Inne nowe typy w .NET Framework 4, na przykład i System.Threading.BarrierSystem.Threading.SpinLock, zapewniają funkcje, które nie były dostępne we wcześniejszych wersjach. Aby uzyskać więcej informacji, zobacz Struktury danych na potrzeby programowania równoległego.

Niestandardowe typy zadań

Zalecamy, aby nie dziedziczyć z elementów System.Threading.Tasks.Task ani System.Threading.Tasks.Task<TResult>. Zamiast tego zalecamy użycie właściwości w celu skojarzenia AsyncState dodatkowych danych lub stanu z obiektem Task lub Task<TResult> . Można również użyć metod rozszerzeń, aby rozszerzyć funkcjonalność Task klas i Task<TResult> . Aby uzyskać więcej informacji na temat metod rozszerzeń, zobacz Metody rozszerzeń i Metody rozszerzeń.

Jeśli musisz dziedziczyć z Task klasy lub Task<TResult>, nie możesz użyć RunSystem.Threading.Tasks.TaskFactoryklasy , System.Threading.Tasks.TaskFactory<TResult>lub System.Threading.Tasks.TaskCompletionSource<TResult> , do utworzenia wystąpień niestandardowego typu zadania. Nie można ich używać, ponieważ te klasy tworzą tylko obiekty Task i Task<TResult> . Ponadto nie można używać mechanizmów kontynuacji zadań udostępnianych przez Task, Task<TResult>, TaskFactoryi TaskFactory<TResult> do tworzenia wystąpień niestandardowego typu zadania. Nie można ich używać, ponieważ te klasy również tworzą tylko obiekty Task i Task<TResult> .

Tytuł Opis
Tworzenie łańcuchów zadań przy użyciu zadań kontynuacji Opisuje, jak działa kontynuacja.
Dołączone i odłączone zadania podrzędne Opisano różnicę między zadaniami podrzędnymi dołączonymi i odłączonymi.
Anulowanie zadania Opisuje obsługę anulowania wbudowaną w Task obiekt .
Obsługa wyjątków Opisuje sposób obsługi wyjątków w wątkach współbieżnych.
Instrukcje: Wykonywanie operacji równoległych za pomocą elementu Parallel.Invoke W tym artykule opisano sposób używania polecenia Invoke.
Instrukcje: Zwracanie wartości z zadania Zawiera opis sposobu zwracania wartości z zadań.
Instrukcje: Anulowanie zadania i jego elementów podrzędnych Opisuje, jak anulować zadania.
Instrukcje: Tworzenie wstępnie obliczonych zadań Opisuje sposób użycia Task.FromResult metody do pobierania wyników asynchronicznych operacji pobierania przechowywanych w pamięci podręcznej.
Instrukcje: Przenoszenie drzewa binarnego z zadaniami równoległymi Opisuje używanie zadań do przechodzenia w drzewie binarnym.
Instrukcje: Odpakowywanie zadania zagnieżdżonego Pokazuje, jak używać Unwrap metody rozszerzenia.
Równoległość danych Opisuje sposób użycia For i ForEach tworzenia pętli równoległych na danych.
Programowanie równoległe Węzeł najwyższego poziomu dla .NET Framework programowania równoległego.

Zobacz też