Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Sie können das aufgabenbasierte asynchrone Muster (TAP) auf drei Arten implementieren: mithilfe der C#- und Visual Basic Compiler in Visual Studio, manuell oder über eine Kombination aus Compiler und manuellen Methoden. In den folgenden Abschnitten werden die einzelnen Methoden ausführlich erläutert. Sie können das TAP-Muster verwenden, um sowohl computegebundene als auch E/A-gebundene asynchrone Vorgänge zu implementieren. Im Abschnitt "Workloads " werden die einzelnen Vorgangstypen erläutert.
Generieren von TAP-Methoden
Verwenden der Compiler
Ab .NET Framework 4.5 gilt jede Methode, die dem Schlüsselwort async (Async in Visual Basic) zugeordnet wird, als asynchrone Methode. Die Compiler C# und Visual Basic führen die erforderlichen Transformationen aus, um die Methode asynchron mithilfe von TAP zu implementieren. Eine asynchrone Methode sollte ein System.Threading.Tasks.Task oder ein System.Threading.Tasks.Task<TResult> Objekt zurückgeben. Für letzteres sollte der Textkörper der Funktion einen TResultzurückgeben, und der Compiler stellt sicher, dass dieses Ergebnis über das resultierende Aufgabenobjekt verfügbar gemacht wird. Entsprechend werden alle Ausnahmen, die im Text der Methode nicht behandelt werden, zur Ausgabeaufgabe gemarshallt und führen dazu, dass die resultierende Aufgabe im Zustand TaskStatus.Faulted endet. Die Ausnahme dieser Regel ist, wenn ein OperationCanceledException (oder abgeleiteter Typ) nicht behandelt wird, in diesem Fall endet der resultierende Vorgang im TaskStatus.Canceled Zustand.
Vorgang.Start und Entsorgung von Vorgängen
Verwenden Sie Start nur für Aufgaben, die explizit mit einem Task-Konstruktor erstellt wurden und sich noch im Created-Zustand befinden. Öffentliche TAP-Methoden sollten aktive Aufgaben zurückgeben, sodass Aufrufer Start nicht aufrufen müssen.
Verwerfen Sie im meisten TAP-Code keine Aufgaben. A Task enthält keine nicht verwalteten Ressourcen im typischen Fall, und das Löschen jedes Vorgangs erhöht mehr Aufwand ohne praktischen Nutzen. Entsorgen Sie nur, wenn bestimmte APIs oder Messungen eine Notwendigkeit dafür zeigen.
Wenn Sie mit der Hintergrundarbeit beginnen, die den unmittelbaren Anrufpfad überlebt, behalten Sie den Besitz explizit bei und verfolgen Sie den Abschluss. Weitere Anleitungen finden Sie unter Beibehalten asynchroner Methoden.
Manuelles Generieren von TAP-Methoden
Sie können das TAP-Muster manuell implementieren, um die Implementierung besser zu steuern. Der Compiler nutzt den öffentlichen Oberflächenbereich, der über den System.Threading.Tasks-Namespace verfügbar gemacht wird, und unterstützende Typen im System.Runtime.CompilerServices-Namespace. Um das TAP selbst zu implementieren, erstellen Sie ein TaskCompletionSource<TResult> Objekt, führen Sie den asynchronen Vorgang aus, und wenn dieser abgeschlossen ist, rufen Sie die SetResult, SetException oder SetCanceled Methode oder die Try Version einer dieser Methoden auf. Wenn Sie eine TAP-Methode manuell implementieren, müssen Sie die resultierende Aufgabe abschließen, wenn der dargestellte asynchrone Vorgang abgeschlossen ist. Beispiel:
static class StreamExtensions
{
public static Task<int> ReadTask(this Stream stream, byte[] buffer, int offset, int count, object? state)
{
var tcs = new TaskCompletionSource<int>();
stream.BeginRead(buffer, offset, count, ar =>
{
try { tcs.SetResult(stream.EndRead(ar)); }
catch (Exception exc) { tcs.SetException(exc); }
}, state);
return tcs.Task;
}
}
Module StreamExtensions
<Extension()>
Public Function ReadTask(stream As Stream, buffer As Byte(),
offset As Integer, count As Integer,
state As Object) As Task(Of Integer)
Dim tcs As New TaskCompletionSource(Of Integer)()
stream.BeginRead(buffer, offset, count,
Sub(ar)
Try
tcs.SetResult(stream.EndRead(ar))
Catch exc As Exception
tcs.SetException(exc)
End Try
End Sub, state)
Return tcs.Task
End Function
End Module
Hybridansatz
Möglicherweise ist es hilfreich, das TAP-Muster manuell zu implementieren, aber delegieren Sie die Kernlogik für die Implementierung an den Compiler. Sie können z. B. den Hybridansatz verwenden, wenn Sie Argumente außerhalb einer vom Compiler generierten asynchronen Methode überprüfen möchten, damit Ausnahmen dem direkten Aufrufer der Methode escapen können, anstatt über das System.Threading.Tasks.Task Objekt verfügbar gemacht zu werden:
class Calculator
{
private int value = 0;
public Task<int> MethodAsync(string input)
{
if (input == null) throw new ArgumentNullException(nameof(input));
return MethodAsyncInternal(input);
}
private async Task<int> MethodAsyncInternal(string input)
{
// code that uses await goes here
await Task.Delay(1);
return value;
}
}
Class Calculator
Private value As Integer = 0
Public Function MethodAsync(input As String) As Task(Of Integer)
If input Is Nothing Then Throw New ArgumentNullException(NameOf(input))
Return MethodAsyncInternal(input)
End Function
Private Async Function MethodAsyncInternal(input As String) As Task(Of Integer)
' code that uses await goes here
Await Task.Delay(1)
Return value
End Function
End Class
Ein weiterer Fall, in dem eine solche Delegierung nützlich ist, ist, wenn Sie schnelle Pfadoptimierung implementieren und eine zwischengespeicherte Aufgabe zurückgeben möchten.
Arbeitsbelastungen
Sie können sowohl computegebundene als auch E/A-gebundene asynchrone Vorgänge als TAP-Methoden implementieren. Wenn Sie jedoch TAP-Methoden öffentlich aus einer Bibliothek verfügbar machen, stellen Sie sie nur für Arbeitslasten bereit, die E/A-gebundene Vorgänge umfassen. Diese Vorgänge können auch berechnungen umfassen, sollten aber nicht rein rechnerisch sein. Wenn eine Methode rein computegebunden ist, machen Sie sie nur als synchrone Implementierung verfügbar. Der ausführende Code kann dann auswählen, ob ein Aufruf dieser synchronen Methode in eine Aufgabe eingebettet werden soll, um die Arbeit auf einen anderen Thread auszulagern oder Parallelität zu erreichen. Wenn eine Methode E/A-gebunden ist, machen Sie sie nur als asynchrone Implementierung verfügbar.
Computegebundene Aufgaben
Die System.Threading.Tasks.Task Klasse eignet sich gut für die Darstellung rechenintensiver Vorgänge. Standardmäßig nutzt sie spezielle Unterstützung innerhalb der ThreadPool Klasse, um eine effiziente Ausführung zu ermöglichen. Außerdem bietet sie erhebliche Kontrolle darüber, wann, wo und wie asynchrone Berechnungen ausgeführt werden.
Generieren Sie computegebundene Aufgaben auf folgende Weise:
Verwenden Sie in .NET Framework 4.5 und höheren Versionen (einschließlich .NET Core und .NET 5+) die statische Task.Run Methode als Verknüpfung zu TaskFactory.StartNew. Verwenden Sie Run zum einfachen Starten einer rechenintensiven Aufgabe, die auf den Threadpool abzielt. Diese Methode ist der bevorzugte Mechanismus zum Starten einer computegebundenen Aufgabe. Verwenden Sie
StartNewnur direkt, wenn Sie eine genauere Kontrolle über den Vorgang wünschen.Verwenden Sie in .NET Framework 4 die methode TaskFactory.StartNew. Sie akzeptiert einen Delegaten (in der Regel ein Action<T> oder ein Func<TResult>), der asynchron ausgeführt wird. Wenn Sie einen Action<T> Delegaten bereitstellen, gibt die Methode ein System.Threading.Tasks.Task Objekt zurück, das die asynchrone Ausführung dieses Delegaten darstellt. Wenn Sie einen Func<TResult> Delegaten angeben, gibt die Methode ein System.Threading.Tasks.Task<TResult> Objekt zurück. Überladungen der StartNew Methode akzeptieren ein Abbruchtoken (CancellationToken), Aufgabenerstellungsoptionen (TaskCreationOptions) und einen Aufgabenplaner (TaskScheduler). Diese Parameter bieten eine differenzierte Kontrolle über die Planung und Ausführung des Vorgangs. Eine Factoryinstanz, die auf den aktuellen Aufgabenplaner abzielt, ist als statische Eigenschaft (Factory) der Task Klasse verfügbar. Beispiel:
Task.Factory.StartNew(…).Verwenden Sie die Konstruktoren des
TaskTyps und dieStartMethode, wenn Sie den Vorgang separat generieren und planen möchten. Öffentliche Methoden dürfen nur Aufgaben zurückgeben, die bereits gestartet wurden.Verwenden Sie die Überladungen der Task.ContinueWith-Methode. Diese Methode erstellt einen neuen Vorgang, der geplant wird, wenn eine andere Aufgabe abgeschlossen wird. Einige der ContinueWith-Überladungen akzeptieren ein Abbruchtoken, Fortsetzungsmöglichkeiten und einen Aufgabenplaner für eine bessere Steuerung der Planung und Ausführung der Fortsetzungsaufgabe.
Verwenden Sie die TaskFactory.ContinueWhenAll Und TaskFactory.ContinueWhenAny Methoden. Diese Methoden erstellen einen neuen Vorgang, der geplant wird, wenn alle oder eine der angegebenen Aufgaben abgeschlossen ist. Diese Methoden stellen auch Überladungen bereit, um die Planung und Ausführung dieser Aufgaben zu steuern.
Bei rechengebundenen Vorgängen kann das System die Ausführung eines geplanten Vorgangs verhindern, wenn er eine Abbruchanforderung empfängt, bevor er mit der Ausführung der Aufgabe beginnt. Wenn Sie beispielsweise ein Abbruchtoken (CancellationToken Objekt) bereitstellen, können Sie dieses Token an den asynchronen Code übergeben, der das Token überwacht. Sie können das Token auch einer der zuvor erwähnten Methoden, wie z. B. StartNew oder Run, übergeben, damit das Task Laufzeitsystem das Token ebenfalls überwachen kann.
Betrachten Sie beispielsweise eine asynchrone Methode, mit der ein Bild gerendert wird. Der Textkörper der Aufgabe kann das Abbruchtoken abfragen, sodass der Code frühzeitig beendet wird, wenn während des Renderings eine Abbruchanforderung eingeht. Wenn die Abbruchanforderung vor dem Rendern eingeht, möchten Sie außerdem den Renderingvorgang verhindern:
internal static Task<Bitmap> RenderAsync(ImageData data, CancellationToken cancellationToken)
{
return Task.Run(() =>
{
var bmp = new Bitmap(data.Width, data.Height);
for (int y = 0; y < data.Height; y++)
{
cancellationToken.ThrowIfCancellationRequested();
for (int x = 0; x < data.Width; x++)
{
// render pixel [x,y] into bmp
}
}
return bmp;
}, cancellationToken);
}
Friend Function RenderAsync(data As ImageData, cancellationToken As CancellationToken) As Task(Of Bitmap)
Return Task.Run(Function()
Dim bmp As New Bitmap(data.Width, data.Height)
For y As Integer = 0 To data.Height - 1
cancellationToken.ThrowIfCancellationRequested()
For x As Integer = 0 To data.Width - 1
' render pixel [x,y] into bmp
Next
Next
Return bmp
End Function, cancellationToken)
End Function
Note
In diesem Beispiel wird Bitmap verwendet, der das Paket System.Drawing.Common erfordert und nur für Windows unterstützt wird. Das rechengebundene Aufgabenmuster, das Task.Run mit einem CancellationToken verwendet, gilt für alle Plattformen. Verwenden Sie eine plattformübergreifende Bildbearbeitungsbibliothek für nicht-Windows-Ziele.
Rechenintensive Vorgänge enden in einem Canceled Status, wenn mindestens eine der folgenden Bedingungen zutrifft:
Eine Abbruchanforderung wird über das CancellationToken-Objekt übermittelt, das als Argument für die Erstellungsmethode bereitgestellt wird (z. B.
StartNewoderRun), bevor die Aufgabe in den Running-Zustand übergeht.Eine OperationCanceledException Ausnahme wird im Textkörper einer solchen Aufgabe nicht behandelt. Diese Ausnahme enthält dasselbe CancellationToken, das an die Aufgabe übergeben wird, und das Token zeigt an, dass eine Abbruchanforderung vorliegt.
Wenn eine andere Ausnahme im Textkörper der Aufgabe nicht behandelt wird, endet die Aufgabe im Faulted Zustand. Alle Versuche, auf die Aufgabe zu warten oder auf das Ergebnis zuzugreifen, bewirkt, dass eine Ausnahme ausgelöst wird.
E/A-gebundene Tasks
Verwenden Sie den TaskCompletionSource<TResult> Typ, um eine Aufgabe zu erstellen, die nicht direkt einen Thread für die gesamte Ausführung verwenden soll. Dieser Typ macht eine Task Eigenschaft verfügbar, die eine zugeordnete Task<TResult> Instanz zurückgibt. Sie steuern den Lebenszyklus dieser Aufgabe mithilfe von TaskCompletionSource<TResult> Methoden wie SetResult, , SetException, SetCanceledund deren TrySet Varianten.
Angenommen, Sie möchten eine Aufgabe erstellen, die nach einem bestimmten Zeitraum abgeschlossen wird. Sie können beispielsweise eine Aktivität auf der Benutzeroberfläche verzögern. Die System.Threading.Timer Klasse bietet bereits die Möglichkeit, einen Delegaten nach einem bestimmten Zeitraum asynchron aufzurufen. Mithilfe von TaskCompletionSource<TResult> können Sie eine Task<TResult> Abdeckung am Timer anbringen. Beispiel:
public static Task<DateTimeOffset> Delay(int millisecondsTimeout)
{
TaskCompletionSource<DateTimeOffset>? tcs = null;
Timer? timer = null;
timer = new Timer(delegate
{
timer!.Dispose();
tcs!.TrySetResult(DateTimeOffset.UtcNow);
}, null, Timeout.Infinite, Timeout.Infinite);
tcs = new TaskCompletionSource<DateTimeOffset>(timer);
timer.Change(millisecondsTimeout, Timeout.Infinite);
return tcs.Task;
}
Public Function Delay(millisecondsTimeout As Integer) As Task(Of DateTimeOffset)
Dim tcs As TaskCompletionSource(Of DateTimeOffset) = Nothing
Dim timer As Timer = Nothing
timer = New Timer(Sub(obj)
timer.Dispose()
tcs.TrySetResult(DateTimeOffset.UtcNow)
End Sub, Nothing, Timeout.Infinite, Timeout.Infinite)
tcs = New TaskCompletionSource(Of DateTimeOffset)(timer)
timer.Change(millisecondsTimeout, Timeout.Infinite)
Return tcs.Task
End Function
Die Task.Delay Methode wird zu diesem Zweck bereitgestellt. Sie können sie innerhalb einer anderen asynchronen Methode verwenden, beispielsweise, um eine asynchrone Polling-Schleife zu implementieren.
public static async Task Poll(Uri url, CancellationToken cancellationToken, IProgress<bool> progress)
{
while (true)
{
await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken);
bool success = false;
try
{
await DownloadStringAsync(url);
success = true;
}
catch { /* ignore errors */ }
progress.Report(success);
}
}
Public Async Function Poll(url As Uri, cancellationToken As CancellationToken,
progress As IProgress(Of Boolean)) As Task
Do While True
Await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken)
Dim success As Boolean = False
Try
Await DownloadStringAsync(url)
success = True
Catch
' ignore errors
End Try
progress.Report(success)
Loop
End Function
Die TaskCompletionSource<TResult> Klasse verfügt nicht über ein nicht generisches Gegenstück.
Task<TResult> leitet sich von Task ab, sodass Sie das generische TaskCompletionSource<TResult>-Objekt für I/O-gebundene Methoden verwenden können, die einfach eine Aufgabe zurückgeben. Verwenden Sie dazu eine Quelle mit einem Dummy TResult (Boolean ist eine gute Standardauswahl, aber falls Sie Bedenken haben, dass Benutzer die Task auf ein Task<TResult> anwenden könnten, können Sie stattdessen einen privaten TResult Typ verwenden). Die Methode im vorherigen Beispiel gibt z. B Delay . die aktuelle Uhrzeit zusammen mit dem resultierenden Offset (Task<DateTimeOffset>) zurück. Wenn ein solcher Ergebniswert nicht erforderlich ist, könnte die Methode stattdessen wie folgt codiert werden (beachten Sie die Änderung des Rückgabetyps und die Änderung des Arguments in TrySetResult):
public static Task<bool> DelaySimple(int millisecondsTimeout)
{
TaskCompletionSource<bool>? tcs = null;
Timer? timer = null;
timer = new Timer(delegate
{
timer!.Dispose();
tcs!.TrySetResult(true);
}, null, Timeout.Infinite, Timeout.Infinite);
tcs = new TaskCompletionSource<bool>(timer);
timer.Change(millisecondsTimeout, Timeout.Infinite);
return tcs.Task;
}
Public Function DelaySimple(millisecondsTimeout As Integer) As Task(Of Boolean)
Dim tcs As TaskCompletionSource(Of Boolean) = Nothing
Dim timer As Timer = Nothing
timer = New Timer(Sub(obj)
timer.Dispose()
tcs.TrySetResult(True)
End Sub, Nothing, Timeout.Infinite, Timeout.Infinite)
tcs = New TaskCompletionSource(Of Boolean)(timer)
timer.Change(millisecondsTimeout, Timeout.Infinite)
Return tcs.Task
End Function
Gemischte rechengebundene und E/A-gebundene Aufgaben
Asynchrone Methoden sind nicht nur auf computegebundene oder E/A-gebundene Vorgänge beschränkt. Sie können eine Mischung aus den beiden darstellen. Tatsächlich kombinieren Sie häufig mehrere asynchrone Vorgänge in größeren gemischten Vorgängen. Beispielsweise führt die RenderAsync Methode in einem vorherigen Beispiel einen rechenintensiven Vorgang aus, um ein Bild basierend auf einigen Eingaben imageDatazu rendern. Dies imageData kann von einem Webdienst stammen, auf den Sie asynchron zugreifen:
public static async Task<Bitmap> DownloadDataAndRenderImageAsync(CancellationToken cancellationToken)
{
var imageData = await DownloadImageDataAsync(cancellationToken);
return await RenderAsync(imageData, cancellationToken);
}
Public Async Function DownloadDataAndRenderImageAsync(cancellationToken As CancellationToken) As Task(Of Bitmap)
Dim imageData As ImageData = Await DownloadImageDataAsync(cancellationToken)
Return Await RenderAsync(imageData, cancellationToken)
End Function
Note
In diesem Beispiel wird Bitmap verwendet, der das Paket System.Drawing.Common erfordert und nur für Windows unterstützt wird. Das Muster, einen asynchronen Download mit einem asynchronen, rechenintensiven Vorgang zu verketten, gilt auf allen Plattformen. Verwenden Sie eine plattformübergreifende Bildbibliothek für Nicht-Windows-Ziele.
In diesem Beispiel wird auch veranschaulicht, wie ein einzelnes Abbruch-Token über mehrere asynchrone Vorgänge durchlaufen werden kann. Weitere Informationen finden Sie im Abschnitt zur Verwendung von Abbruchtokens unter Verwenden des aufgabenbasierten asynchronen Musters.