Programowanie asynchroniczne oparte na zadaniach
Biblioteka równoległa zadań (TPL) opiera się na koncepcji zadania , który reprezentuje operację asynchroniczną. Pod pewnymi względami zadanie przypomina wątek lub element pracy ThreadPool, ale na wyższym poziomie abstrakcji. Termin równoległość zadań odnosi się do jednego lub więcej niezależnych zadań działających równocześnie. Zadania zapewniają dwie podstawowe korzyści:
Wydajniejsze i bardziej skalowalne wykorzystanie zasobów systemowych.
W tle zadania są kolejkowane do ThreadPool, który został ulepszony algorytmami określającymi i dostosowującymi 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 drobiazgowego równoległego przetwarzania.
Większa kontrola programistyczna niż w przypadku wątku lub elementu roboczego.
Zadania i struktura utworzona wokół nich zapewniają bogaty zestaw interfejsów API, które obsługują oczekiwanie, anulowanie, kontynuacje, niezawodną obsługę wyjątków, szczegółowy stan, planowanie niestandardowe i inne.
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ć delegata Action dla każdego zadania. Najprostszym sposobem utworzenia tych delegatów jest użycie wyrażeń lambda. Wyrażenie lambda może wywołać nazwaną metodę lub podać wbudowany kod. W poniższym przykładzie przedstawiono podstawowe wywołanie Invoke, 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
.
Notatka
Ta dokumentacja używa wyrażeń lambda do definiowania delegatów w języku 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())
Notatka
Liczba wystąpień Task tworzonych w tle przez 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.
Aby uzyskać większą kontrolę nad wykonywaniem zadań lub zwrócić wartość z zadania, należy pracować z obiektami Task bardziej szczegółowo.
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 klasę System.Threading.Tasks.Task<TResult>, która dziedziczy z Task. Obiekt zadania obsługuje szczegóły infrastruktury i udostępnia metody i właściwości, które są dostępne z wątku wywołującego przez cały okres istnienia zadania. Na przykład można uzyskać dostęp do właściwości zadania Status w dowolnym momencie, aby określić, czy zostało uruchomione, zakończone, anulowane lub zgłosiło wyjątek. Stan jest reprezentowany przez wyliczenie TaskStatus.
Podczas tworzenia zadania należy przypisać mu delegata użytkownika, który zawiera kod wykonywany przez zadanie. Delegat może być wyrażony jako nazwany delegat, metoda anonimowa lub wyrażenie lambda. Wyrażenia lambda mogą zawierać wywołanie metody nazwanej, jak pokazano w poniższym przykładzie. Przykład zawiera wywołanie metody Task.Wait, aby upewnić 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ć metod Task.Run, aby utworzyć i uruchomić zadanie w jednej operacji. Aby zarządzać zadaniem, metody Run 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ć metody TaskFactory.StartNew, 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óry możesz pobrać za pośrednictwem jego właściwości Task.AsyncState.
using System;
using System.Threading;
using System.Threading.Tasks;
namespace TaskIntro;
class CustomData
{
public long CreationTime;
public int Name;
public int ThreadNum;
}
public class AsyncState
{
public static void Main()
{
Task[] taskArray = new Task[10];
for (int i = 0; i < taskArray.Length; i++)
{
taskArray[i] = Task.Factory.StartNew((Object obj) =>
{
CustomData data = obj as CustomData;
if (data == null) return;
data.ThreadNum = Thread.CurrentThread.ManagedThreadId;
},
new CustomData() { Name = i, CreationTime = DateTime.Now.Ticks });
}
Task.WaitAll(taskArray);
foreach (var task in taskArray)
{
var data = task.AsyncState as CustomData;
if (data != null)
Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",
data.Name, data.CreationTime, data.ThreadNum);
}
}
}
// The example displays output like the following:
// Task #0 created at 635116412924597583, ran on thread #3.
// Task #1 created at 635116412924607584, ran on thread #4.
// Task #2 created at 635116412924607584, ran on thread #4.
// Task #3 created at 635116412924607584, ran on thread #4.
// Task #4 created at 635116412924607584, ran on thread #3.
// Task #5 created at 635116412924607584, ran on thread #3.
// Task #6 created at 635116412924607584, ran on thread #4.
// Task #7 created at 635116412924607584, ran on thread #4.
// Task #8 created at 635116412924607584, ran on thread #3.
// Task #9 created at 635116412924607584, ran on thread #4.
Imports System.Threading
Namespace AsyncState
Class CustomData
Public CreationTime As Long
Public Name As Integer
Public ThreadNum As Integer
End Class
Module Example
Public Sub Main()
Dim taskArray(9) As Task
For i As Integer = 0 To taskArray.Length - 1
taskArray(i) = Task.Factory.StartNew(Sub(obj As Object)
Dim data As CustomData = TryCast(obj, CustomData)
If data Is Nothing Then Return
data.ThreadNum = Environment.CurrentManagedThreadId
End Sub,
New CustomData With {.Name = i, .CreationTime = Date.Now.Ticks})
Next
Task.WaitAll(taskArray)
For Each task In taskArray
Dim data = TryCast(task.AsyncState, CustomData)
If data IsNot Nothing Then
Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",
data.Name, data.CreationTime, data.ThreadNum)
End If
Next
End Sub
End Module
' The example displays output like the following:
' Task #0 created at 635116412924597583, ran on thread #3.
' Task #1 created at 635116412924607584, ran on thread #4.
' Task #2 created at 635116412924607584, ran on thread #4.
' Task #3 created at 635116412924607584, ran on thread #4.
' Task #4 created at 635116412924607584, ran on thread #3.
' Task #5 created at 635116412924607584, ran on thread #3.
' Task #6 created at 635116412924607584, ran on thread #4.
' Task #7 created at 635116412924607584, ran on thread #4.
' Task #8 created at 635116412924607584, ran on thread #3.
' Task #9 created at 635116412924607584, ran on thread #4.
End Namespace
Task i Task<TResult> każdy eksponują statyczną właściwość Factory, która zwraca domyślne wystąpienie 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 publiczną właściwość Task<TResult>.Result zawierającą wynik obliczeń. Zadania są uruchamiane asynchronicznie i mogą zostać wykonane w dowolnej kolejności. Jeśli dostęp do właściwości Result 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.
Gdy używasz 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ż zmienia się po każdej iteracji. Poniższy przykład ilustruje problem. Przekazuje licznik pętli do wyrażenia lambda, które tworzy wystąpienie obiektu CustomData
i używa licznika pętli jako identyfikatora obiektu. Jak pokazuje dane wyjściowe z przykładu, każdy obiekt CustomData
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
Aby uzyskać dostęp do wartości w każdej iteracji, należy podać obiekt stanu do zadania za pomocą jego konstruktora. Przy użyciu licznika pętli poniższy przykład modyfikuje poprzedni przykład podczas tworzenia obiektu CustomData
, który z kolei jest przekazywany do wyrażenia lambda. Jak pokazano w danych wyjściowych z przykładu, każdy obiekt CustomData
ma teraz unikatowy identyfikator, opierając się na wartości licznika pętli w chwili instancjonowania 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 właściwości Task.AsyncState. Poniższy przykład to odmiana poprzedniego przykładu. Używa właściwości AsyncState do wyświetlania informacji o obiektach CustomData
przekazywanych 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 właściwości Task.Id. Identyfikator jest przydatny do wyświetlania informacji o zadaniu w debugerze programu Visual Studio w oknach Parallel Stacks i Tasks. 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 Korzystanie z okna zadań i Korzystanie z okna stosów równoległych.
Opcje tworzenia zadań
Większość interfejsów API, które tworzą zadania, oferuje przeciążenia akceptujące parametr TaskCreationOptions. Określając co najmniej jedną z tych opcji, informujesz harmonogram zadań, jak zaplanować zadanie na puli wątków. Opcje mogą być łączone przy użyciu bitowej operacji LUB.
W poniższym przykładzie przedstawiono zadanie z opcjami LongRunning 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 skojarzona kulturę i kulturę interfejsu użytkownika, która jest definiowana odpowiednio przez właściwości Thread.CurrentCulture i Thread.CurrentUICulture. Kultura wątku jest używana w takich operacjach, jak formatowanie, analizowanie, sortowanie i porównywanie ciągów. Kultura UI wątku jest używana przy 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 właściwości CultureInfo.DefaultThreadCurrentCulture 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 korzystają z kultury wywołującego wątku, nawet jeśli są 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 walutowe 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 €
Notatka
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" (Kultura i operacje oparte na zadaniach asynchronicznych) w artykule CultureInfo.
Tworzenie kontynuacji zadań
Metody Task.ContinueWith i Task<TResult>.ContinueWith umożliwiają określenie zadania, które ma być uruchamiane po zakończeniu zadania . Delegat zadania kontynuacji jest przekazywane odwołanie do zadania poprzedzającego, aby mógł zbadać stan zadania poprzedzającego. A po pobraniu wartości właściwości Task<TResult>.Result, można użyć danych wyjściowych poprzednika jako danych wejściowych kontynuacji.
W poniższym przykładzie zadanie getData
jest uruchamiane przez wywołanie metody TaskFactory.StartNew<TResult>(Func<TResult>). Zadanie processData
jest uruchamiane automatycznie po zakończeniu getData
i displayData
jest uruchamiane po zakończeniu processData
.
getData
tworzy tablicę całkowitą, która jest dostępna dla zadania processData
za pośrednictwem właściwości Task<TResult>.Result zadania getData
. Zadanie processData
przetwarza tę tablicę i zwraca wynik, którego typ jest wywnioskowany z zwracanego typu wyrażenia lambda przekazanego do metody Task<TResult>.ContinueWith<TNewResult>(Func<Task<TResult>,TNewResult>). Zadanie displayData
jest wykonywane automatycznie po zakończeniu processData
, a obiekt Tuple<T1,T2,T3> zwrócony przez wyrażenie lambda processData
jest dostępny dla zadania displayData
za pośrednictwem właściwości Task<TResult>.Result zadania processData
. Zadanie displayData
pobiera wynik zadania processData
. Tworzy wynik, którego typ jest wywnioskowany w podobny sposób, i który jest udostępniany programowi we właściwości Result.
using System;
using System.Threading.Tasks;
public class ContinuationOne
{
public static void Main()
{
var getData = Task.Factory.StartNew(() => {
Random rnd = new Random();
int[] values = new int[100];
for (int ctr = 0; ctr <= values.GetUpperBound(0); ctr++)
values[ctr] = rnd.Next();
return values;
} );
var processData = getData.ContinueWith((x) => {
int n = x.Result.Length;
long sum = 0;
double mean;
for (int ctr = 0; ctr <= x.Result.GetUpperBound(0); ctr++)
sum += x.Result[ctr];
mean = sum / (double) n;
return Tuple.Create(n, sum, mean);
} );
var displayData = processData.ContinueWith((x) => {
return String.Format("N={0:N0}, Total = {1:N0}, Mean = {2:N2}",
x.Result.Item1, x.Result.Item2,
x.Result.Item3);
} );
Console.WriteLine(displayData.Result);
}
}
// The example displays output similar to the following:
// N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82
Namespace ContinuationsOne
Module Example
Public Sub Main()
Dim getData = Task.Factory.StartNew(Function()
Dim rnd As New Random()
Dim values(99) As Integer
For ctr = 0 To values.GetUpperBound(0)
values(ctr) = rnd.Next()
Next
Return values
End Function)
Dim processData = getData.ContinueWith(Function(x)
Dim n As Integer = x.Result.Length
Dim sum As Long
Dim mean As Double
For ctr = 0 To x.Result.GetUpperBound(0)
sum += x.Result(ctr)
Next
mean = sum / n
Return Tuple.Create(n, sum, mean)
End Function)
Dim displayData = processData.ContinueWith(Function(x)
Return String.Format("N={0:N0}, Total = {1:N0}, Mean = {2:N2}",
x.Result.Item1, x.Result.Item2,
x.Result.Item3)
End Function)
Console.WriteLine(displayData.Result)
End Sub
End Module
' The example displays output like the following:
' N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82
End Namespace
Ponieważ Task.ContinueWith jest metodą instancji, możesz połączyć wywołania metod zamiast tworzenia instancji obiektu Task<TResult> dla każdego poprzedzającego zadania. 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 wykonywania wielu zadań.
Aby uzyskać więcej informacji, zobacz Łączenie zadań przez użycie zadań kontynuacji.
Tworzenie oddzielonych zadań podrzędnych
Gdy kod użytkownika uruchomiony w zadaniu tworzy nowe zadanie i nie określa opcji AttachedToParent, nowe zadanie nie jest synchronizowane z zadaniem nadrzędnym w żaden specjalny sposób. Ten typ niezsynchronizowanego zadania jest nazywany odłączonym zagnieżdżonym zadaniem lub odłączonym zadaniem 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.
Notatka
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 zadaniem podrzędnym zadania nadrzędnego. Możesz użyć funkcji AttachedToParent, aby wyrazić strukturalną równoległość zadań, ponieważ zadanie nadrzędne w sposób ukryty czeka na zakończenie wszystkich dołączonych zadań podrzędnych. W poniższym przykładzie pokazano zadanie nadrzędne, które tworzy 10 powiązanych zadań podrzędnych. Przykład wywołuje metodę Task.Wait, aby poczekać na zakończenie zadania nadrzędnego. Nie musi wprost czekać na zakoń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ć opcji TaskCreationOptions.DenyChildAttach, aby uniemożliwić dołączanie innych zadań do zadania nadrzędnego. Aby uzyskać więcej informacji, zobacz wiązane i odłączane zadania podrzędne.
Oczekiwanie na zakończenie zadań
Typy System.Threading.Tasks.Task i System.Threading.Tasks.Task<TResult> oferują kilka przeciążeń metod Task.Wait, które umożliwiają oczekiwanie na zakończenie zadania. Ponadto przeciążenia statycznych metod Task.WaitAll i Task.WaitAny umożliwiają oczekiwanie na zakończenie dowolnej lub całej tablicy zadań.
Zazwyczaj należy poczekać na zadanie z jednego z następujących powodów:
Główny wątek zależy od końcowego wyniku obliczonego przez zadanie.
Należy obsługiwać wyjątki, jakie mogą być rzucane z zadania.
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 w
Main
(punkt 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ługi wyjątków.
Niektóre przeciążenia umożliwiają określenie limitu czasu, a inne przyjmują dodatkowy parametr wejściowy CancellationToken, aby można było anulować oczekiwanie za pomocą programu lub w odpowiedzi na dane wejściowe użytkownika.
Gdy czekasz na zadanie, niejawnie czekasz również na wszystkie jego podzadania, które zostały utworzone przy użyciu opcji TaskCreationOptions.AttachedToParent. Task.Wait zwraca natychmiast, jeśli zadanie już zostało ukończone. Metoda Task.Wait zgłosi wszelkie wyjątki zgłoszone przez zadanie, nawet jeśli metoda Task.Wait 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 metody WhenAll, WhenAny, Delayi FromResult.
Task.WhenAll
Metoda Task.WhenAll asynchronicznie czeka na zakończenie wielu obiektów Task lub Task<TResult>. Udostępnia przeciążone wersje, które umożliwiają oczekiwanie na niejednorodne zestawy zadań. Na przykład można poczekać na wykonanie wielu obiektów Task i Task<TResult> z jednego wywołania metody.
Task.WhenAny
Metoda Task.WhenAny asynchronicznie czeka na zakończenie jednego z wielu obiektów Task lub Task<TResult>. Podobnie jak w metodzie Task.WhenAll, ta metoda udostępnia przeciążone wersje, które umożliwiają oczekiwanie na niejednolite zestawy zadań. Metoda WhenAny jest szczególnie przydatna 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ć metody WhenAny, aby wybrać operację, która zakończy się najpierw, a następnie anulować pozostałe operacje.
Operacje przeplatane: Można rozpocząć wiele operacji, które muszą się zakończyć, a następnie użyć metody WhenAny do przetwarzania wyników w miarę kończenia się poszczególnych operacji. Po zakończeniu jednej operacji można uruchomić co najmniej jedno zadanie.
Ograniczone operacje: można użyć metody WhenAny, aby rozszerzyć poprzedni scenariusz, ograniczając liczbę jednoczesnych operacji.
Wygasłe operacje: możesz użyć metody WhenAny do wybrania 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 Delay została opisana w poniższej sekcji.
Task.Delay
Metoda Task.Delay tworzy obiekt Task, 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.
Zadanie (T). FromResult
Korzystając z metody Task.FromResult, można utworzyć obiekt Task<TResult>, który zawiera wstępnie obliczony wynik. Ta metoda jest przydatna podczas wykonywania operacji asynchronicznej zwracającej obiekt Task<TResult>, a wynik tego obiektu Task<TResult> jest już obliczany. Aby uzyskać przykład użycia FromResult do pobierania wyników asynchronicznych operacji pobierania, które są przechowywane w pamięci podręcznej, zobacz Jak tworzyć wstępnie obliczone zadania.
Obsługa wyjątków w zadaniach
Gdy zadanie zgłasza co najmniej jeden wyjątek, wyjątki są opakowane w wyjątek AggregateException. Wyjątek ten jest przekazywany z powrotem do wątku, który dołącza do zadania. Zazwyczaj jest to wątek czekający na zakończenie zadania lub wątek uzyskujący dostęp do właściwości Result. To zachowanie wymusza politykę .NET Framework, zgodnie z którą domyślnie wszelkie 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 bloku try
/catch
:
Wątek łączący może również obsługiwać wyjątki, uzyskując dostęp do właściwości Exception, zanim zadanie zostanie poddane procesowi oczyszczania pamięci. Uzyskując dostęp do tej właściwości, uniemożliwiasz nieobsługiwanemu wyjątkowi wyzwolenie zachowania propagacji wyjątku, które kończy proces w momencie finalizacji 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 klasie System.Threading.Tasks.Task przyjmuje obiekt CancellationToken jako parametr wejściowy. Wiele przeciążeń StartNew i Run obejmuje również parametr CancellationToken.
Token można utworzyć i wysłać żądanie anulowania w późniejszym czasie przy użyciu klasy CancellationTokenSource. Przekaż token do Task jako argument, a także odwołaj się do tego samego tokenu w delegacie użytkownika, który odpowiada na żądanie anulowania.
Aby uzyskać więcej informacji, zobacz Anulowanie zadania i Jak anulować zadanie i jego podzadania.
Klasa TaskFactory
Klasa TaskFactory udostępnia metody statyczne, które hermetyzują typowe wzorce tworzenia i uruchamiania zadań oraz zadań kontynuacji.
Najczęstszym wzorcem jest StartNew, który tworzy i uruchamia zadanie w jednej instrukcji.
Podczas tworzenia zadań kontynuacji na podstawie wielu przeddentów należy użyć metody ContinueWhenAll lub metody ContinueWhenAny lub ich odpowiedników w klasie Task<TResult>. Aby uzyskać więcej informacji, zobacz Łączenie zadań za pomocą zadań kontynuacji.
Aby hermetyzować metody asynchronicznego modelu programowania
BeginX
iEndX
w instancji Task lub Task<TResult>, użyj metod FromAsync. Aby uzyskać więcej informacji, zobacz TPL i Tradycyjne programowanie asynchroniczne programu .NET Framework.
Domyślna wartość TaskFactory może być dostępna jako właściwość statyczna w klasie Task lub Task<TResult>. Możesz również bezpośrednio utworzyć instancję TaskFactory i wybrać różne opcje, które obejmują CancellationToken, opcję TaskCreationOptions, opcję TaskContinuationOptions lub TaskScheduler. Jakiekolwiek opcje zostaną określone podczas tworzenia fabryki zadań, zostaną one zastosowane do wszystkich tworzonych zadań, chyba że Task zostanie utworzona wykorzystując wyliczenie TaskCreationOptions, w takim przypadku opcje zadania zastępują opcje fabryki zadań.
Zadania bez delegatów
W niektórych przypadkach można użyć Task do hermetyzacji niektórych operacji asynchronicznych wykonywanych 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ć metod FromAsync. Jeśli tak nie jest, możesz użyć obiektu TaskCompletionSource<TResult>, aby umieścić operację w zadaniu, a tym samym uzyskać niektóre korzyści wynikające z Task programowalności. 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 przejmuje się, na którym procesorze uruchamiane jest zadanie, jak synchronizuje ono swoją pracę z innymi zadaniami ani jak jest ono zaplanowane na System.Threading.ThreadPool. Wymagają one tylko, aby było wykonywane tak wydajnie, jak to możliwe na komputerze hosta. 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.
Powiązane struktury danych
TPL ma kilka nowych typów publicznych, które są przydatne w równoległych i sekwencyjnych scenariuszach. Obejmują one kilka klas kolekcji bezpiecznych wątkowo, szybkich i skalowalnych w przestrzeni nazw System.Collections.Concurrent 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, 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 System.Threading.Tasks.Task ani System.Threading.Tasks.Task<TResult>. Zamiast tego zalecamy użycie właściwości AsyncState w celu skojarzenia dodatkowych danych lub stanu z obiektem Task lub Task<TResult>. Można również użyć metod rozszerzeń, aby rozszerzyć funkcjonalność klas Task i Task<TResult>. Aby uzyskać więcej informacji na temat metod rozszerzeń, zobacz metody rozszerzenia i metody rozszerzeń .
Jeśli musisz dziedziczyć z Task lub Task<TResult>, nie możesz użyć klas Run ani System.Threading.Tasks.TaskFactory, System.Threading.Tasks.TaskFactory<TResult>, ani System.Threading.Tasks.TaskCompletionSource<TResult> do tworzenia 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 tworzą tylko obiekty Task i Task<TResult>.
Powiązane sekcje
Tytuł | Opis |
---|---|
Łączenie zadań przy użyciu zadań kontynuacyjnych | Opisuje sposób działania kontynuacji. |
Dołączone i Odłączone Zadania Podrzędne | Opisuje różnicę między dołączonymi i odłączonymi zadaniami podrzędnymi. |
Anulowanie zadania | Opisuje obsługę anulowania wbudowaną w obiekt Task. |
Obsługa wyjątków | Opisuje sposób obsługi wyjątków w wątkach współbieżnych. |
Instrukcje: używanie metody Parallel.Invoke do wykonywania operacji równoległych | Opisuje sposób używania Invoke. |
Jak: Zwrócić Wartość z Zadania | Opisuje sposób zwracania wartości z zadań. |
Instrukcje: anulowanie zadania i jego elementów podrzędnych | Opisuje sposób anulowania zadań. |
Instrukcje: tworzenie wstępnie obliczonych zadań | Opisuje sposób użycia metody Task.FromResult w celu pobrania wyników asynchronicznych operacji pobierania przechowywanych w pamięci podręcznej. |
Jak przechodzić przez drzewo binarne przy użyciu zadań równoległych | Opisuje sposób używania zadań do przechodzenia przez drzewo binarne. |
Instrukcje: odpakowywanie zagnieżdżonego zadania | Pokazuje, jak używać metody rozszerzenia Unwrap. |
równoległość danych | Opisuje sposób używania For i ForEach do tworzenia pętli równoległych na danych. |
Programowanie równoległe | Węzeł najwyższego poziomu dla programowania równoległego w .NET Framework. |