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.
In .NET ist das aufgabenbasierte asynchrone Muster das empfohlene asynchrone Entwurfsmuster für die neue Entwicklung. Es basiert auf den Typen Task und Task<TResult> im System.Threading.Tasks Namespace, die verwendet werden, um asynchrone Vorgänge darzustellen.
Benennen, Parameter und Rückgabetypen
TAP verwendet eine einzelne Methode, um die Initiierung und den Abschluss eines asynchronen Vorgangs darzustellen. Dies unterscheidet sich sowohl mit dem Muster für die asynchrone Programmierung (APM oder IAsyncResult
) als auch mit dem ereignisbasierten asynchronen Muster (AAP). APM erfordert Begin
und End
Methoden. EAP erfordert eine Methode, die das Async
-Suffix hat, und auch mindestens ein Ereignis, einen Ereignishandler-Delegattypen und aus EventArg
abgeleitete Typen. Asynchrone Methoden in TAP enthalten das Suffix Async
nach dem Vorgangsnamen für Methoden, die wartbare Typen zurückgeben, z. B. Task, Task<TResult>, ValueTask und ValueTask<TResult>. Beispielsweise kann ein asynchroner Get
Vorgang, der einen Task<String>
Namen zurückgibt, benannt GetAsync
werden. Wenn Sie einer Klasse, die bereits einen EAP-Methodennamen mit dem Async
Suffix enthält, eine TAP-Methode hinzufügen, verwenden Sie stattdessen das Suffix TaskAsync
. Wenn die Klasse beispielsweise bereits über eine GetAsync
Methode verfügt, verwenden Sie den Namen GetTaskAsync
. Wenn eine Methode einen asynchronen Vorgang startet, aber keinen wartenden Typ zurückgibt, sollte der Name mit Begin
, Start
oder einem anderen Verb beginnen, um vorzuschlagen, dass diese Methode das Ergebnis des Vorgangs nicht zurückgibt oder auslöst.
Eine TAP-Methode gibt entweder ein System.Threading.Tasks.Task oder ein System.Threading.Tasks.Task<TResult> zurück, basierend darauf, ob die entsprechende synchrone Methode "void" oder einen Typ TResult
zurückgibt.
Die Parameter einer TAP-Methode sollten den Parametern des synchronen Gegenstücks entsprechen und in derselben Reihenfolge bereitgestellt werden. Allerdings sind die Parameter out
und ref
von dieser Regel ausgenommen und sollten vollständig vermieden werden. Alle Daten, die über einen out
oder ref
Parameter zurückgegeben worden wären, sollten stattdessen als Teil des TResult
, das von Task<TResult> zurückgegeben wird, bereitgestellt werden. Dabei sollte ein Tupel oder eine benutzerdefinierte Datenstruktur verwendet werden, um mehrere Werte aufzunehmen. Erwägen Sie außerdem das Hinzufügen eines CancellationToken Parameters, auch wenn das synchrone Gegenstück der TAP-Methode keines bietet.
Methoden, die ausschließlich der Erstellung, Manipulation oder Kombination von Aufgaben gewidmet sind, brauchen nicht diesem Benennungsmuster zu entsprechen, wenn die asynchrone Absicht der Methode im Methodennamen oder im Namen des Typs, zu dem die Methode gehört, deutlich ist; solche Methoden werden häufig als Kombinatoren bezeichnet. Beispiele für Kombinatoren sind WhenAll und WhenAny, und sie werden im Abschnitt "Verwendung der integrierten aufgabenbasierten Kombinatoren" des Artikels "Verbrauch des aufgabenbasierten asynchronen Musters" erläutert.
Beispiele dafür, wie sich die TAP-Syntax von der Syntax unterscheidet, die in älteren asynchronen Programmiermustern verwendet wird, z. B. asynchrones Programmiermodell (APM) und das ereignisbasierte asynchrone Muster (AAP), finden Sie unter "Asynchrone Programmiermuster".
Initiieren eines asynchronen Vorgangs
Eine asynchrone Methode, die auf TAP basiert, kann eine kleine Menge an Arbeit synchron durchführen, z. B. das Überprüfen von Argumenten und das Initiieren des asynchronen Vorgangs, bevor sie die resultierende Task zurückgibt. Synchrone Arbeiten sollten auf ein Minimum beschränkt werden, damit die asynchrone Methode schnell zurückgeben kann. Gründe für eine schnelle Rückgabe sind:
Asynchrone Methoden können von Benutzeroberflächenthreads aufgerufen werden, und jede lange ausgeführte synchrone Arbeit kann die Reaktionsfähigkeit der Anwendung beeinträchtigen.
Mehrere asynchrone Methoden können gleichzeitig gestartet werden. Daher kann jede lange ausgeführte Arbeit im synchronen Teil einer asynchronen Methode die Initiierung anderer asynchroner Vorgänge verzögern und dadurch die Vorteile der Parallelität verringern.
In einigen Fällen ist die zum Abschließen des Vorgangs erforderliche Arbeitsmenge kleiner als die Zum asynchronen Starten des Vorgangs erforderliche Arbeitsmenge. Das Lesen aus einem Datenstrom, in dem der Lesevorgang durch Bereits im Arbeitsspeicher gepufferte Daten erfüllt werden kann, ist ein Beispiel für ein solches Szenario. In solchen Fällen kann der Vorgang synchron abgeschlossen werden und kann einen Vorgang zurückgeben, der bereits abgeschlossen wurde.
Ausnahmen
Eine asynchrone Methode sollte nur Ausnahmen auslösen, die aufgrund eines Verwendungsfehlers beim Aufruf der asynchronen Methode ausgelöst werden. Verwendungsfehler sollten nie im Produktionscode auftreten. Wenn beispielsweise ein Nullverweis (Nothing
in Visual Basic) als Argument einer Methode einen Fehlerzustand erzeugt (normalerweise durch eine ArgumentNullException Ausnahme dargestellt), können Sie den Code des Aufrufs so ändern, dass kein Nullverweis übergeben wird. Bei allen anderen Fehlern sollten Ausnahmen, die auftreten, wenn eine asynchrone Methode ausgeführt wird, der zurückgegebenen Aufgabe zugewiesen werden, auch wenn die asynchrone Methode synchron abgeschlossen wird, bevor die Aufgabe zurückgegeben wird. In der Regel enthält eine Aufgabe höchstens eine Ausnahme. Wenn die Aufgabe jedoch mehrere Vorgänge darstellt (z. B WhenAll. ), können mehrere Ausnahmen einem einzelnen Vorgang zugeordnet werden.
Zielumgebung
Wenn Sie eine TAP-Methode implementieren, können Sie bestimmen, wo die asynchrone Ausführung erfolgt. Sie können die Workload im Threadpool ausführen, sie mithilfe asynchroner E/A implementieren (ohne für den Großteil der Ausführung des Vorgangs an einen Thread gebunden zu sein), sie auf einem bestimmten Thread (z. B. dem UI-Thread) auszuführen oder eine beliebige Anzahl potenzieller Kontexte zu verwenden. Eine TAP-Methode kann sogar nichts ausführen und kann nur ein Task Element zurückgeben, das das Auftreten einer Bedingung an anderer Stelle im System darstellt (z. B. eine Aufgabe, die Daten darstellt, die in einer Warteschlange-Datenstruktur ankommen).
Der Aufrufer der TAP-Methode kann das Warten auf die TAP-Methode blockieren, indem er synchron auf den resultierenden Vorgang wartet oder beim Abschluss des asynchronen Vorgangs zusätzlichen Code (Fortsetzungscode) ausführt. Der Ersteller des Fortsetzungscodes hat die Kontrolle darüber, wo dieser Code ausgeführt wird. Sie können den Fortsetzungscode entweder explizit über Methoden der Task-Klasse (z. B. ContinueWith) oder implizit mithilfe der Sprachunterstützung erstellen, die auf Fortsetzungen basiert (z. B. await
in C#, Await
in Visual Basic, AwaitValue
in F#).
Vorgangsstatus
Die Task Klasse stellt einen Lebenszyklus für asynchrone Vorgänge bereit, und dieser Zyklus wird durch die TaskStatus Enumeration dargestellt. Um Eckfälle von Typen zu unterstützen, die von Task und Task<TResult> abgeleitet sind, und um die Trennung von Konstruktion und Planung zu ermöglichen, bietet die Task-Klasse eine Start-Methode an. Von den öffentlichen Task-Konstruktoren erstellte Tasks werden als inaktive Tasks bezeichnet, da ihr Lebenszyklus im nicht geplanten Zustand Created beginnt und sie erst im geplanten Zustand sind, wenn Start für diese Instanzen aufgerufen wird.
Alle anderen Aufgaben starten ihren Lebenszyklus in einem aktiven Zustand. Das bedeutet, dass die asynchronen Operationen, die sie darstellen, bereits gestartet wurden und ihr Aufgabenstatus einen anderen Enumerationswert als TaskStatus.Created aufweist. Alle Aufgaben, die von TAP-Methoden zurückgegeben werden, müssen aktiviert sein. Wenn eine TAP-Methode intern den Konstruktor einer Aufgabe verwendet, um die zurückgegebene Aufgabe zu instanziieren, muss die TAP-Methode Start auf dem Task-Objekt aufrufen, bevor sie es zurückgibt. Consumer einer TAP-Methode können davon ausgehen, dass die zurückgegebene Aufgabe aktiv ist, und sollten nicht versuchen, Start für einen von einer TAP-Methode zurückgegebenen Task aufzurufen. Der Aufruf von Start für eine aktive Aufgabe führt zu einer InvalidOperationException-Ausnahme.
Stornierung (optional)
In TAP ist die Stornierung sowohl für asynchrone Methodenimplementierer als auch für asynchrone Methoden-Verbraucher optional. Wenn ein Vorgang einen Abbruch zulässt, macht er eine Überladung der asynchronen Methode verfügbar, die ein Abbruchtoken akzeptiert (CancellationToken-Instanz). Standardmäßig wird der Parameter benannt cancellationToken
.
public Task ReadAsync(byte [] buffer, int offset, int count,
CancellationToken cancellationToken)
Public Function ReadAsync(buffer() As Byte, offset As Integer,
count As Integer,
cancellationToken As CancellationToken) _
As Task
Der asynchrone Vorgang überwacht dieses Token auf Abbruchanforderungen. Wenn sie eine Stornierungsanforderung empfängt, kann sie sich entscheiden, diese Anforderung zu berücksichtigen und den Vorgang abzubrechen. Wenn die Abbruchanforderung dazu führt, dass die Arbeit vorzeitig beendet wird, gibt die TAP-Methode eine Aufgabe zurück, die im Zustand Canceled endet. Es gibt kein verfügbares Ergebnis, und es wird keine Ausnahme ausgelöst. Der Canceled-Zustand wird als endgültiger (abgeschlossener) Zustand für eine Aufgabe angesehen, zusammen mit den Zuständen Faulted und RanToCompletion. Wenn sich ein Vorgang im Canceled Zustand befindet, gibt die IsCompleted Eigenschaft daher zurück true
. Wenn eine Aufgabe im Canceled Status abgeschlossen ist, werden alle mit der Aufgabe registrierten Fortsetzungen geplant oder ausgeführt, es sei denn, eine Fortsetzungsoption wie NotOnCanceled wurde angegeben, um auf die Fortsetzung zu verzichten. Die Ausführung von jedem Code, der mithilfe von Sprachfunktionen asynchron auf eine abgebrochene Aufgabe wartet, wird weiter ausgeführt, aber der Code empfängt eine OperationCanceledException-Ausnahme oder eine von ihr abgeleitete Ausnahme. Synchron blockierter Code, der durch Methoden wie Wait und WaitAll auf die Aufgabe wartet, wird ebenso weiter mit einer Ausnahme ausgeführt.
Wenn ein Abbruchtoken die Stornierung angefordert hat, bevor die TAP-Methode, die dieses Token akzeptiert, aufgerufen wird, sollte die TAP-Methode eine Canceled Aufgabe zurückgeben. Wenn der Abbruch jedoch angefordert wird, während der asynchrone Vorgang ausgeführt wird, muss der asynchrone Vorgang die Abbruchanforderung nicht akzeptieren. Der zurückgegebene Vorgang sollte nur dann im Canceled Zustand enden, wenn der Vorgang aufgrund der Abbruchanforderung beendet wird. Wenn der Abbruch angefordert wird, aber noch ein Ergebnis oder eine Ausnahme erzeugt wird, sollte die Aufgabe im RanToCompletion oder Faulted Zustand enden.
Für asynchrone Methoden, die als Erstes abgebrochen können werden sollen, müssen Sie keine Überladung bereitstellen, die kein Abbruchtoken akzeptiert. Für Methoden, die nicht abgebrochen werden können, stellen Sie keine Überladungen bereit, die ein Abbruchtoken akzeptieren; Dies hilft dem Aufrufer anzugeben, ob die Zielmethode tatsächlich abgebrochen werden kann. Verbrauchercode, der keine Stornierung wünscht, kann eine Methode aufrufen, die einen CancellationToken akzeptiert und None als Argumentwert bereitstellt. None entspricht der Standardeinstellung CancellationToken.
Statusberichte (optional)
Einige asynchrone Vorgänge profitieren von der Bereitstellung von Statusbenachrichtigungen; Diese werden in der Regel verwendet, um eine Benutzeroberfläche mit Informationen zum Fortschritt des asynchronen Vorgangs zu aktualisieren.
In TAP wird der Fortschritt über eine IProgress<T> Schnittstelle behandelt, die an die asynchrone Methode als Parameter übergeben wird, der normalerweise benannt progress
wird. Durch das Bereitstellen der Statusschnittstelle zum Zeitpunkt des Aufrufs der asynchronen Methode lassen sich leichter Racebedingungen vermeiden, die aus falscher Verwendung resultieren (d. h., wenn Ereignishandler, die nach dem Aufruf des Vorgangs nicht ordnungsgemäß registriert wurden, möglicherweise Updates verpassen). Vor allem unterstützt die Statusschnittstelle jedoch verschiedene Implementierungen des Status, die durch den verwendeten Code bestimmt werden. Beispielsweise soll der verwendete Code nur das neueste Statusupdate berücksichtigen oder alle Updates puffern oder für jedes Update eine Aktion aufrufen oder das Marshallen eines Aufrufs in einen bestimmten Thread steuern. All diese Optionen können mithilfe einer anderen Implementierung der Schnittstelle erreicht werden, die an die Bedürfnisse des jeweiligen Verbrauchers angepasst ist. Wie beim Abbruch sollten TAP-Implementierungen nur dann einen IProgress<T> Parameter bereitstellen, wenn die API Statusbenachrichtigungen unterstützt.
Wenn z. B. die ReadAsync
weiter oben in diesem Artikel erläuterte Methode den Zwischenfortschritt in Form der bisher gelesenen Anzahl von Bytes melden kann, könnte der Statusrückruf eine IProgress<T> Schnittstelle sein:
public Task ReadAsync(byte[] buffer, int offset, int count,
IProgress<long> progress)
Public Function ReadAsync(buffer() As Byte, offset As Integer,
count As Integer,
progress As IProgress(Of Long)) As Task
Wenn eine FindFilesAsync
Methode eine Liste aller Dateien zurückgibt, die einem bestimmten Suchmuster entsprechen, könnte der Statusrückruf eine Schätzung des Prozentsatzes der abgeschlossenen Arbeit und den aktuellen Satz von Teilergebnissen liefern. Diese Informationen können entweder als Tuple:
public Task<ReadOnlyCollection<FileInfo>> FindFilesAsync(
string pattern,
IProgress<Tuple<double,
ReadOnlyCollection<List<FileInfo>>>> progress)
Public Function FindFilesAsync(pattern As String,
progress As IProgress(Of Tuple(Of Double, ReadOnlyCollection(Of List(Of FileInfo))))) _
As Task(Of ReadOnlyCollection(Of FileInfo))
oder mit einem Datentyp, der für die API spezifisch ist:
public Task<ReadOnlyCollection<FileInfo>> FindFilesAsync(
string pattern,
IProgress<FindFilesProgressInfo> progress)
Public Function FindFilesAsync(pattern As String,
progress As IProgress(Of FindFilesProgressInfo)) _
As Task(Of ReadOnlyCollection(Of FileInfo))
Im letzteren Fall ist der spezielle Datentyp in der Regel mit dem Suffix ProgressInfo
versehen.
Wenn TAP-Implementierungen Überladungen bereitstellen, die einen progress
-Parameter akzeptieren, müssen sie das Argument null
zulassen; in diesem Fall wird kein Fortschritt angezeigt. TAP-Implementierungen sollten den Fortschritt synchron an das Progress<T> Objekt melden, wodurch die asynchrone Methode den Fortschritt schnell bereitstellen kann. Außerdem ermöglicht es dem Verbraucher des Fortschritts zu bestimmen, wie und wo die Informationen am besten behandelt werden können. Zum Beispiel kann die Statusinstanz Rückrufe marshallen und Ereignisse auf einem aufgezeichneten Synchronisierungskontext auslösen.
IProgress<T-Implementierungen>
.NET stellt die Progress<T>-Klasse bereit, die IProgress<T> implementiert. Die Progress<T> Klasse wird wie folgt deklariert:
public class Progress<T> : IProgress<T>
{
public Progress();
public Progress(Action<T> handler);
protected virtual void OnReport(T value);
public event EventHandler<T>? ProgressChanged;
}
Eine Instanz von Progress<T> stellt ein ProgressChanged Ereignis bereit, das jedes Mal ausgelöst wird, wenn der asynchrone Vorgang ein Fortschrittsupdate meldet. Das ProgressChanged Ereignis wird für das SynchronizationContext Objekt ausgelöst, das beim Instanziieren der Progress<T> Instanz erfasst wurde. Wenn kein Synchronisierungskontext verfügbar war, wird ein Standardkontext verwendet, der auf den Threadpool abzielt. Handler können mit diesem Ereignis registriert werden. Ein einzelner Handler kann auch dem Progress<T> Konstruktor zur Vereinfachung bereitgestellt werden und verhält sich genauso wie ein Ereignishandler für das ProgressChanged Ereignis. Statusupdates werden asynchron ausgelöst, um den asynchronen Vorgang zu verzögern, während der Ereignishandler ausgeführt wird. Eine andere IProgress<T> Implementierung könnte verschiedene Semantik anwenden.
Auswählen der bereitzustellenden Überladungen
Wenn eine TAP-Implementierung sowohl die optionalen CancellationToken als auch die optionalen IProgress<T> Parameter verwendet, kann es bis zu vier Überladungen erfordern:
public Task MethodNameAsync(…);
public Task MethodNameAsync(…, CancellationToken cancellationToken);
public Task MethodNameAsync(…, IProgress<T> progress);
public Task MethodNameAsync(…,
CancellationToken cancellationToken, IProgress<T> progress);
Public MethodNameAsync(…) As Task
Public MethodNameAsync(…, cancellationToken As CancellationToken cancellationToken) As Task
Public MethodNameAsync(…, progress As IProgress(Of T)) As Task
Public MethodNameAsync(…, cancellationToken As CancellationToken,
progress As IProgress(Of T)) As Task
Viele TAP-Implementierungen bieten jedoch keine Abbruch- oder Fortschrittsfunktionen, daher ist eine einzige Methode erforderlich:
public Task MethodNameAsync(…);
Public MethodNameAsync(…) As Task
Wenn eine TAP-Implementierung entweder Abbruch oder Status, jedoch nicht beides unterstützt, kann sie zwei Überladungen bereitstellen:
public Task MethodNameAsync(…);
public Task MethodNameAsync(…, CancellationToken cancellationToken);
// … or …
public Task MethodNameAsync(…);
public Task MethodNameAsync(…, IProgress<T> progress);
Public MethodNameAsync(…) As Task
Public MethodNameAsync(…, cancellationToken As CancellationToken) As Task
' … or …
Public MethodNameAsync(…) As Task
Public MethodNameAsync(…, progress As IProgress(Of T)) As Task
Wenn eine TAP-Implementierung Abbruch und Status unterstützt, kann sie alle vier Überladungen verfügbar machen. Sie kann jedoch nur die folgenden beiden Bereitstellen:
public Task MethodNameAsync(…);
public Task MethodNameAsync(…,
CancellationToken cancellationToken, IProgress<T> progress);
Public MethodNameAsync(…) As Task
Public MethodNameAsync(…, cancellationToken As CancellationToken,
progress As IProgress(Of T)) As Task
Um die beiden fehlenden Zwischenkombinationen auszugleichen, können Entwickler None oder einen Standardwert CancellationToken für den cancellationToken
Parameter und null
für den progress
Parameter übergeben.
Wenn Sie erwarten, dass jede Verwendung der TAP-Methode einen Abbruch oder Status unterstützt, können Sie die Überladungen weglassen, die den relevanten Parameter nicht akzeptieren.
Wenn Sie mehrere Überladungen verfügbar machen, um einen Abbruch oder Status als optional zu konfigurieren, sollte das Verhalten der Überladungen, die einen Abbruch oder Status nicht unterstützen, dem Verhalten einer Überladung entsprechen, die None für den Abbruch oder null
für den Status an die Überladung übergibt, die diese Vorgänge jeweils unterstützt.
Verwandte Artikel
Titel | BESCHREIBUNG |
---|---|
Asynchrone Programmiermuster | Führt die drei Muster zum Ausführen asynchroner Vorgänge ein: das aufgabenbasierte asynchrone Muster (TAP), das asynchrone Programmiermodell (APM) und das ereignisbasierte asynchrone Muster (EAP). |
Implementieren des aufgabenbasierten asynchronen Musters | Beschreibt, wie das aufgabenbasierte asynchrone Muster (TAP) auf drei Arten implementiert wird: mithilfe der C#- und Visual Basic-Compiler in Visual Studio, manuell oder über eine Kombination aus Compiler und manuellen Methoden. |
Nutzen des aufgabenbasierten asynchronen Musters | Beschreibt, wie Sie Aufgaben und Rückrufe verwenden können, um eine Verzögerung ohne Blockierung zu erreichen. |
Interoperabilität mit anderen asynchronen Mustern und Typen | Beschreibt, wie Das aufgabenbasierte asynchrone Muster (Tap) zum Implementieren des asynchronen Programmiermodells (APM) und des ereignisbasierten asynchronen Musters (AAP) verwendet wird. |