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.
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ć ContinueWhenAll metody lub ContinueWhenAny metody lub ich odpowiedników w Task<TResult> klasie. Aby uzyskać więcej informacji, zobacz Łączenie zadań za pomocą zadań kontynuacji.
Aby hermetyzować asynchroniczny model
BeginX
programowania iEndX
metody w wystąpieniu TaskFromAsync, Task<TResult> użyj metod . Aby uzyskać więcej informacji, zobacz TPL i Tradycyjne programowanie asynchroniczne programu .NET Framework.
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.
Powiązane struktury danych
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> .
Sekcje pokrewne
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. |