Udostępnij za pośrednictwem


Programowanie asynchroniczne oparte na zadaniach

Biblioteka równoległa zadań (TPL) opiera się na koncepcji zadania, które reprezentuje operację asynchroniczną. Pod pewnymi względami zadanie przypomina wątek lub ThreadPool element roboczy, ale na wyższym poziomie abstrakcji. Termin równoległość zadań odnosi się do jednego lub większej liczby niezależnych zadań uruchomionych jednocześnie. Zadania zapewniają dwie podstawowe korzyści:

  • Efektywniejsze i bardziej skalowalne wykorzystanie zasobów systemowych.

    W tle zadania są kolejkowane do ThreadPoolobiektu , który został rozszerzony o algorytmy określające i dostosowujące 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 język 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 jednoczesnego uruchamiania dowolnej liczby dowolnych instrukcji. Wystarczy przekazać Action delegata 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 pokazano 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 w tle Invoke nie musi być równa liczbie udostępnionych delegatów. TPL może stosować różne optymalizacje, zwłaszcza 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 funkcji Parallel.Invoke do wykonywania operacji równoległych).

Aby uzyskać większą kontrolę nad wykonywaniem zadań 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żesz uzyskać dostęp do Status właściwości zadania w dowolnym momencie, aby określić, czy uruchomiono, uruchomiono do ukończenia, zostało anulowane, czy zgłosiło wyjątek. Stan jest reprezentowany TaskStatus przez 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. Przykład zawiera wywołanie Task.Wait metody w celu upewnienia się, że zadanie zakończy wykonywanie przed zakończeniem działania aplikacji w trybie 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 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

Można również użyć Task.Run metod do utworzenia i uruchomienia zadania 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 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

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żesz użyć tej metody, gdy:

  • Tworzenie i planowanie nie musi być oddzielone i wymagasz dodatkowych opcji tworzenia zadań lub użycia 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 a Task<TResult> każda uwidacznia właściwość statyczną Factory zwracającą domyślne wystąpienie TaskFactoryklasy , aby można było wywołać metodę jako Task.Factory.StartNew(). Ponadto w poniższym przykładzie, ponieważ zadania mają typ System.Threading.Tasks.Task<TResult>, każdy z nich ma właściwość publiczną Task<TResult>.Result zawierającą wynik obliczeń. Zadania są uruchamiane asynchronicznie i mogą zostać wykonane w dowolnej kolejności. Result Jeśli dostęp do właściwości jest uzyskiwany 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 Jak zwrócić wartość 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 wyciszana 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 pokazuje dane wyjściowe 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 liczb całkowitych, 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 Stosy równoległe i zadania debugera programu Visual Studio. Identyfikator jest tworzony leniwie, co oznacza, że nie jest tworzony, dopóki nie zostanie on żądany. 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 Zadań) i Using the Parallel Stacks Window (Korzystanie z 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 przy użyciu 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 domyślną kulturę i kulturę interfejsu użytkownika wątku. Można jednak określić domyślną kulturę 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 podrzędne używają kultury wywołującego wątku, nawet jeśli zadanie jest uruchamiane asynchronicznie w innym wątku.

Poniższy przykład przedstawia prostą ilustrację. Zmienia bieżącą kulturę aplikacji na francuski (Francja). Jeśli język 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. Bez względu na to, czy delegat jest wywoływany przez zadanie synchronicznie, czy asynchronicznie, zadanie używa kultury wątku wywołującego.

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 programu .NET Framework wcześniejszych 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 antecedent, aby mógł zbadać stan zadania przedzidentowego. A po pobraniu wartości Task<TResult>.Result właściwości można użyć danych wyjściowych antecedent 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 pośrednictwem getData właściwości zadania Task<TResult>.Result . Zadanie processData przetwarza tę 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 zwracany 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 metodą wystąpienia, można połączyć wywołania metod łańcuchowe zamiast utworzyć wystąpienie Task<TResult> obiektu dla każdego zadania przedzibowego. Poniższy przykład jest funkcjonalnie identyczny z poprzednim, z tą różnicą, że łączy ze sobą wywołania Task.ContinueWith metody . Task<TResult> Obiekt 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. Tego typu zadanie niezsynchronizowane jest nazywane zagnieżdżonym zadaniem 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 AttachedToParent opcją , 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. Przykład wywołuje metodę Task.Wait , aby poczekać 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ń.

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 zostać zakończona przed ukończeniem wykonywania wszystkich zadań. Na przykład aplikacje konsolowe zakończą działanie po wykonaniu całego synchronicznego kodu ( Main punktu 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ć sam 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 funkcja 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 udostępnia 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 przeplatane: możesz uruchomić wiele operacji, które muszą zakończyć działanie i użyć metody w celu przetworzenia wyników po zakończeniu WhenAny każdej operacji. Po zakończeniu jednej operacji można uruchomić co najmniej jedno zadanie.

  • Operacje ograniczone: możesz użyć WhenAny metody w celu rozszerzenia poprzedniego scenariusza, ograniczając liczbę 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 zakończonym po określonym czasie, takim jak zadanie zwrócone przez metodę Delay . Metoda została opisana Delay 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 zwracającej Task<TResult> obiekt, a wynik tego Task<TResult> obiektu jest już obliczany. Przykład używany FromResult do pobierania wyników asynchronicznych operacji pobierania przechowywanych w pamięci podręcznej można znaleźć w temacie How to: Create Pre-Computed Tasks (Instrukcje: tworzenie wstępnie obliczonych zadań).

Obsługa wyjątków w zadaniach

Gdy zadanie zgłasza co najmniej jeden wyjątek, 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ątku, który uzyskuje Result dostęp do właściwości. To zachowanie wymusza zasady programu .NET Framework, które domyślnie nieobsługiwane wyjątki 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 łączący może również obsługiwać wyjątki, korzystając Exception z właściwości, zanim zadanie zostanie odśmiecane. 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 anulowanie współpracy i jest w pełni zintegrowana z klasami System.Threading.CancellationTokenSource i System.Threading.CancellationToken , które zostały wprowadzone w programie .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 delegacie 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ń podrzędnych 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ą CancellationTokenTaskCreationOptions , 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, aby opakowować operację w zadaniu, a tym samym uzyskać niektóre korzyści związane z Task programowaniem. 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ń domyślnego harmonogramu 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 programie .NET Framework 4, na przykład System.Threading.Barrier i System.Threading.SpinLock, udostępniają funkcje, które nie były dostępne we wcześniejszych wersjach. Aby uzyskać więcej informacji, zobacz Struktury danych dla programowania równoległego.

Niestandardowe typy zadań

Zalecamy, aby nie dziedziczyć z lub System.Threading.Tasks.TaskSystem.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 lub Task<TResult>, nie możesz użyć RunSystem.Threading.Tasks.TaskFactoryklasy lub , System.Threading.Tasks.TaskFactory<TResult>lub System.Threading.Tasks.TaskCompletionSource<TResult> , aby utworzyć wystąpienia 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> .

Nazwa 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ą Task w 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żywania For i ForEach tworzenia pętli równoległych na danych.
Programowanie równoległe Węzeł najwyższego poziomu dla programowania równoległego programu .NET Framework.

Zobacz też