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.
Verwenden Sie aufgabenbasierte asynchrone Methoden, um zu vermeiden, dass ein Thread blockiert wird, während Microsoft.Data.SqlClient auf den Verbindungsaufbau und Netzwerk-I/O wartet. Asynchrone Ausführung macht einen Befehl oder Reader nicht für gleichzeitige Nutzung sicher und verkürzt die Abfrageausführungszeit auf dem Server nicht.
Die Beispiele, die Anwendungstabellen abfragen, verwenden die AdventureWorks-Beispieldatenbank. Die C#-Beispiele erfordern using Microsoft.Data.SqlClient;.
Eine Aufrufkette zu asynchronem Code konvertieren
Konvertiere jeweils einen vollständigen Aufrufpfad:
- Mach die enthaltende Methode asynchron.
- Ersetzen Sie Verbindungs-, Befehls-, Leser- und Transaktionsaufrufe durch ihre aufgabenbasierten Gegenstücke.
-
awaitjede Operation, bevor dasselbe Objekt erneut verwendet wird. - Geben Sie das Abbruchtoken des Anrufers durch jede Operation weiter, die der Anrufer abbrechen kann.
- Legen Sie ein endliches Befehls-Timeout als Grenze für Datenbankvorgänge fest.
Verwenden Sie await ab dem Öffnen der Verbindung bis zur Ergebnisverarbeitung. Der folgende Auszug setzt voraus, dass connectionString und sql bereits definiert sind. Es erzeugt ein CancellationTokenSource mit einem endlichen Timeout für die Operation:
using var cancellation = new CancellationTokenSource(TimeSpan.FromSeconds(30));
await using var connection = new SqlConnection(connectionString);
await connection.OpenAsync(cancellation.Token);
using var command = new SqlCommand(sql, connection);
await using SqlDataReader reader =
await command.ExecuteReaderAsync(cancellation.Token);
while (await reader.ReadAsync(cancellation.Token))
{
Console.WriteLine(reader.GetValue(0));
}
ExecuteReaderAsync kehrt zurück, wenn das erste Ergebnis verfügbar ist, nicht nachdem alle Zeilen angekommen sind. Jeder Aufruf von ReadAsync rückt eine Zeile voran, sodass die Schleife Zeilen verarbeitet, sobald sie eintreffen, und den Thread nur freigibt, während sie auf weitere Daten vom Server wartet. Der 30-Sekunden-Token begrenzt den gesamten Vorgang, einschließlich der Leseschleife.
Es kann nur eine asynchrone Ausführung für einen SqlCommand ausstehend sein. Es kann nur eine asynchrone Lese-, Ergebnisübergangs- oder Feldoperation für einen SqlDataReader ausstehend sein. Warte auf jede Operation, bevor du die nächste Operation am selben Objekt startest.
Asynchrone Programmierungsfeatures
Die folgenden synchronen Methoden haben aufgabenbasierte Gegenstücke, die ein Abbruchtoken akzeptieren:
| Synchrone Methode | Asynchrone Methode | Example |
|---|---|---|
SqlConnection.Open |
SqlConnection.OpenAsync |
Konvertiere eine synchrone Methode |
SqlCommand.ExecuteNonQuery |
SqlCommand.ExecuteNonQueryAsync |
Befehle asynchron ausführen |
SqlCommand.ExecuteScalar |
SqlCommand.ExecuteScalarAsync |
Befehle asynchron ausführen |
SqlCommand.ExecuteXmlReader |
SqlCommand.ExecuteXmlReaderAsync |
Befehle asynchron ausführen |
SqlCommand.ExecuteReader |
SqlCommand.ExecuteReaderAsync |
Einen Aufrufpfad in asynchronen Code konvertieren |
SqlDataReader.Read |
SqlDataReader.ReadAsync |
Einen Aufrufpfad in asynchronen Code konvertieren |
SqlDataReader.NextResult |
SqlDataReader.NextResultAsync |
Ergebnisse asynchron lesen |
SqlDataReader.IsDBNull |
SqlDataReader.IsDBNullAsync |
Ergebnisse asynchron abrufen |
SqlDataReader.GetFieldValue<T> |
SqlDataReader.GetFieldValueAsync<T> |
Ergebnisse asynchron auslesen |
SqlBatch.ExecuteNonQuery, ExecuteScalar und ExecuteReader |
SqlBatch.ExecuteNonQueryAsync, ExecuteScalarAsync und ExecuteReaderAsync |
Batch ausführen |
SqlBulkCopy.WriteToServer |
SqlBulkCopy.WriteToServerAsync |
Verwenden Sie asynchrone Operationen mit SqlBulkCopy |
Verwenden Sie synchrone Entfernung, wenn ein Objekt nur IDisposable implementiert. Verwenden Sie await using für Objekte, die IAsyncDisposable implementieren. Entfernen Sie den Leser, bevor Sie seine Verbindung wiederverwenden.
Befehle asynchron ausführen
Die folgenden Beispiele nehmen an, dass connection offen ist. Verwenden Sie ExecuteScalarAsync für einen einzelnen Wert und geben Sie ein aufrufereigenes Abbruchtoken weiter:
using var cancellation = new CancellationTokenSource(
TimeSpan.FromSeconds(30));
CancellationToken token = cancellation.Token;
using var count = new SqlCommand(
"SELECT COUNT(*) FROM Production.Product;",
connection);
int total = (int)(await count.ExecuteScalarAsync(token))!;
Console.WriteLine($"Row count: {total}");
Verwenden Sie ExecuteNonQueryAsync für datenverändernde Anweisungen. Verwenden Sie ExecuteXmlReaderAsync für eine FOR XML Anfrage. Schließe den XML-Reader, bevor du einen weiteren Befehl auf derselben Verbindung ausführst. Ohne Multiple Active Result Sets (MARS) erlaubt eine Verbindung nur einen geöffneten Reader.
Ergebnisse asynchron auslesen
Das folgende Beispiel übergibt denselben Abbruchtoken an jeden dieser asynchronen Aufrufe:
-
ExecuteReaderAsyncführt die Abfrage aus. -
ReadAsyncliest jede Zeile. -
GetFieldValueAsyncliest jeden Spaltenwert. -
NextResultAsyncwechselt zur nächsten Ergebnismenge. -
IsDBNullAsyncprüft, obNULLvorliegt.
using var cancellation = new CancellationTokenSource(
TimeSpan.FromSeconds(30));
CancellationToken token = cancellation.Token;
using var query = new SqlCommand("""
SELECT TOP (3) ProductID, Name
FROM Production.Product
ORDER BY ProductID;
SELECT TOP (1) ProductID, Color
FROM Production.Product
WHERE Color IS NULL;
""", connection);
await using SqlDataReader reader = await query.ExecuteReaderAsync(token);
while (await reader.ReadAsync(token))
{
int id = await reader.GetFieldValueAsync<int>(0, token);
string name = await reader.GetFieldValueAsync<string>(1, token);
Console.WriteLine($"{id}: {name}");
}
if (await reader.NextResultAsync(token) && await reader.ReadAsync(token))
{
int productId = await reader.GetFieldValueAsync<int>(0, token);
bool colorIsNull = await reader.IsDBNullAsync(1, token);
Console.WriteLine($"{productId}: Color is NULL: {colorIsNull}");
}
NextResultAsync wechselt zur nächsten Ergebnismenge.
GetFieldValueAsync und IsDBNullAsync lesen eine Spalte der aktuellen Zeile. Wenn Sie den Standard CommandBehaviorverwenden, ist die Zeile bereits gepuffert, sodass diese Aufrufe normalerweise ohne Wartezeit abgeschlossen werden. Wenn Sie CommandBehavior.SequentialAccess verwenden, können diese auf Spaltendaten aus dem Netzwerk warten. Weitere Informationen zum sequentiellen Zugriff finden Sie unter SqlClient Streaming-Unterstützung.
Konvertiere eine synchrone Methode
Diese synchrone Methode öffnet eine Verbindung, führt einen Befehl aus und gibt die Anzahl der Zeilen in sys.objectszurück:
static int CountObjects(string connectionString)
{
using var connection = new SqlConnection(connectionString);
connection.Open();
using var command = new SqlCommand("SELECT COUNT(*) FROM sys.objects;", connection);
return (int)command.ExecuteScalar()!;
}
Um die Methode in asynchronen Code umzuwandeln, nehmen Sie folgende Änderungen vor:
- Ändere den Rückgabetyp auf
Task<int>. - Füge einen Parameter
CancellationTokenhinzu. - Ersetzen Sie
OpendurchOpenAsync, und ersetzen SieExecuteScalardurchExecuteScalarAsync. - Geben Sie die Verbindung mit
await usingfrei.
Die umgewandelte Methode sieht so aus:
static async Task<int> CountObjectsAsync(
string connectionString,
CancellationToken cancellationToken)
{
await using var connection = new SqlConnection(connectionString);
await connection.OpenAsync(cancellationToken);
using var command = new SqlCommand("SELECT COUNT(*) FROM sys.objects;", connection);
return (int)(await command.ExecuteScalarAsync(cancellationToken))!;
}
Der Aufrufer erstellt das Token und wartet auf das Ergebnis:
using var cancellation = new CancellationTokenSource(TimeSpan.FromSeconds(30));
int objectCount = await CountObjectsAsync(connectionString, cancellation.Token);
Console.WriteLine($"Objects: {objectCount}");
Beide Methoden liefern die gleiche Zahl. Das Ergebnis hängt von der Datenbank ab, mit der du dich verbindest.
Connect Timeout begrenzt weiterhin den Verbindungsaufbau und die Wartezeiten im Pool. Das Abbrechen des Tokens kann die Ausführung schneller an den Aufrufer zurückgeben.
Fügen Sie asynchrone Aufrufe zu einer bestehenden Anwendung hinzu
Konvertieren Sie jeweils einen kompletten Aufrufpfad. Vermeiden Sie es, GetAwaiter().GetResult(), .Result oder .Wait() bei Datenbankvorgängen aufzurufen, da diese Aufrufe blockieren und in Umgebungen mit einem Synchronisationskontext zu Deadlocks führen können.
Starte keinen asynchronen Befehl und benutze dann denselben Befehl oder Reader synchron, bevor die Aufgabe abgeschlossen ist. Ohne MARS sollten Sie dieselbe Verbindung nicht synchron verwenden, solange die Aufgabe nicht abgeschlossen ist.
Abbruch- und Zeitbeschränkungen hinzufügen
Abbrechen eines asynchronen Vorgangs
Geben Sie denselben Abbruchtoken vom Öffnen der Verbindung bis zur Befehlsausführung weiter:
using var cancellation = new CancellationTokenSource(TimeSpan.FromSeconds(10));
await using var connection = new SqlConnection(connectionString);
await connection.OpenAsync(cancellation.Token);
using var command = new SqlCommand(
"SELECT COUNT(*) FROM Production.Product;",
connection);
await command.ExecuteScalarAsync(cancellation.Token);
Der Abbruch erfolgt nach dem Best-Effort-Prinzip. Die Operation könnte abgeschlossen werden, bevor der Abbruch wirksam wird, und die beobachtete Ausnahme hängt davon ab, wo der Abbruch erfolgt. Gehe nicht davon aus, dass eine Stornierungsanfrage bedeutet, dass der Server abgeschlossene Arbeit zurückgesetzt hat. Wenn eine Menge von Änderungen als Einheit erfolgreich sein oder scheitern muss, verwenden Sie eine explizite Transaktion. Weitere Informationen finden Sie unter Lokale Transaktionen.
Cancel fordert die Aufhebung des aktuellen Befehls an. Es ersetzt keinen vom Aufrufer bereitgestellten Cancellation Token. Die Operation könnte abgeschlossen werden, bevor die Abbruchanfrage in Kraft tritt.
Nach Abbruch oder Zeitüberschreitung warten Sie die Operation ab, und entfernen Sie den Leser und Befehl. Überprüfen Sie den Verbindungszustand vor der Wiederverwendung. Ein Fehler, der die Verbindung unterbricht, erfordert eine neue Verbindung.
Konfigurieren von Timeouts
Verbindungs- und Kommando-Timeouts decken verschiedene Phasen ab:
| Setting | Abdeckungen |
|---|---|
Connect Timeout |
Herstellen einer Verbindung und Warten auf eine gepoolte Verbindung. |
CommandTimeout |
Befehlsausführung und kumulierte Wartezeiten beim Netzwerklesen. |
| Abbruchtoken | Eine vom Anrufer gesteuerte Anforderung, das Warten einzustellen. |
CommandTimeoutin Sekunden gemessen.
0 Das heißt, es gibt kein Kommandozeitlimit. Der Standardwert ist der Command Timeout-Wert der Verbindung, sofern konfiguriert, ansonsten 30 Sekunden.
Bei einem Leser gilt die Befehlszeitüberschreitung für die kumulierte Netzwerkwartezeit jedes Lesevorgangs des Anbieters. Es beinhaltet nicht die Zeit, die mit der Verarbeitung einer Zeile im Anwendungscode verbracht wird. Eine Zeitüberschreitung kann vor der ersten Zeile oder beim Abrufen einer späteren Zeile auftreten.
Abbruch und Befehls-Timeout kombinieren
Verwenden Sie eine begrenzte Befehlszeitüberschreitung und den Abbruch durch den Aufrufer zusammen. Dieses Beispiel setzt voraus, dass connection offen ist:
using var cancellation = new CancellationTokenSource(TimeSpan.FromSeconds(30));
using var command = new SqlCommand("""
SELECT TOP (10) SalesOrderID, OrderDate
FROM Sales.SalesOrderHeader
ORDER BY SalesOrderID;
""", connection)
{
CommandTimeout = 15
};
await using SqlDataReader reader =
await command.ExecuteReaderAsync(cancellation.Token);
while (await reader.ReadAsync(cancellation.Token))
{
Console.WriteLine($"{reader.GetInt32(0)} | {reader.GetDateTime(1):O}");
}
Wenn der Aufrufer zuerst abbricht, löst der abgewartete Aufruf eine Ausnahme aus. Der Ausnahmetyp hängt davon ab, wo der Abbruch erfolgt. Wenn der Kommando-Timeout zuerst abläuft, meldet der Anbieter einen Timeout. In beiden Fällen könnte der Befehl erfolgreich abgeschlossen werden, bevor der Abbruch oder die Zeitüberschreitung wirksam wird.
Fortgeschrittene asynchrone Muster
Asynchrone Methoden über Basisanbietertypen verwenden
Anbieterunabhängiger Code kann DbCommand, DbDataReader und DbConnection asynchrone Methoden verwenden. Erstelle Parameter über den Provider-Befehl und vermeide Downcasts, es sei denn, die Anwendung benötigt eine Microsoft.Data.SqlClient-spezifische Funktion.
Verwenden Sie aufgabenbasierte SQL-Transaktionsmethoden
Verwenden Sie die aufgabenbasierten lokalen Transaktionsmethoden, wenn das Ziel-Framework sie bereitstellt. Das folgende Beispiel geht davon aus, dass connection offen ist, sql den Befehlstext hält und der Aufrufer cancellationToken übergibt:
await using SqlTransaction transaction =
(SqlTransaction)await connection.BeginTransactionAsync(cancellationToken);
using var command = new SqlCommand(sql, connection, transaction);
try
{
await command.ExecuteNonQueryAsync(cancellationToken);
await transaction.CommitAsync(cancellationToken);
}
catch (Exception operationException)
{
try
{
await transaction.RollbackAsync(CancellationToken.None);
}
catch (Exception rollbackException)
{
throw new AggregateException(
"The operation and transaction rollback both failed.",
operationException,
rollbackException);
}
throw;
}
Verwende einen separaten Bereinigungs-Token für ein Rollback. Der Operationstoken könnte bereits storniert sein. Behandle einen Rollback-Fehlschlag, ohne die ursprüngliche Operation-Ausnahme zu verwerfen. Bei diesem Beispiel werden beide Ausnahmen in einer AggregateException beibehalten. Eine Anwendung kann stattdessen die Rollback-Ausnahme protokollieren und die ursprüngliche Ausnahme neu werfen.
Diese Microsoft.Data.SqlClient-Methoden verwenden die vererbten DbConnection- und DbTransaction-Implementierungen, die die synchronen Transaktionsmethoden aufrufen und eine abgeschlossene Task zurückgeben. Das Warten auf sie blockiert den aufrufenden Thread nicht, während die Transaktionsoperation läuft. Der Abbruch wird vor Beginn des Vorgangs überprüft und unterbricht keinen laufenden Vorgang. Die Befehlsausführung innerhalb der Transaktion ist weiterhin asynchron.
Verwenden Sie asynchrone verteilte Transaktionen
Asynchrone Befehle können innerhalb eines TransactionScope ausgeführt werden. Der Scope setzt Transaction.Current, was .NET als ambient-Transaktion bezeichnet. Eine Verbindung, die sich innerhalb des Gültigkeitsbereichs öffnet, wird automatisch bei dieser Transaktion aufgeführt, da das Verbindungs-Schlüsselwort Enlist standardmäßig auf true festgelegt wird. Konfigurieren TransactionScopeAsyncFlowOption.Enabled Sie, wenn die Transaktion über await fließen muss. Verteilte Transaktionen haben Anforderungen an Plattform, Server und Koordinator. Weitere Informationen finden Sie unter System.Transactions-Integration.
Verwenden Sie asynchrone Operationen mit SqlBulkCopy
Verwenden Sie WriteToServerAsync für asynchrone Massenkopien. Halte den Quellleser oder die Zeilensammlung am Leben, bis der zurückgegebene Task abgeschlossen ist. Konfigurieren Sie Batchgröße, Transaktionsverhalten und Timeout für die Operation. Weitere Informationen finden Sie unter Massenkopiervorgänge.
Asynchrones Verwenden mehrerer Befehle mit MARS
MARS erlaubt mehr als einen ausstehenden Befehl auf einer physischen Verbindung. Aktiviere es nur mit MultipleActiveResultSets=True, wenn die Anwendung dieses Verbindungs-Sharing-Modell benötigt.
MARS ist für asynchrone Programmierung nicht erforderlich. Ohne MARS beenden oder entfernen Sie den aktiven Leser, bevor Sie einen weiteren Befehl für dieselbe Verbindung ausführen. Eine zweite Verbindung ist bei wirklich unabhängiger Arbeit oft klarer.
Für Sitzungsverhalten und Transaktionsbeschränkungen siehe Enable Multiple Active Result Sets.
Asynchrones Lesen und Aktualisieren von Daten mit MARS
Wenn Sie MARS aktivieren, kann ein Befehl Zeilen lesen, während ein anderer Befehl für dieselbe Verbindung ausgeführt werden kann. Warten Sie auf jeden Befehl und jeden Lesevorgang einzeln. MARS-Operationen sind nicht threadsicher, und die Serverausführung kann trotzdem an Yield Points serialisieren.
Wenn Lese- und Aktualisierungsoperationen atomar sein müssen, weisen Sie jedem Befehl dieselbe lokale Transaktion zu und testen Sie das MARS-Verhalten der Transaktion. Committe nicht, solange eine andere Operation in dieser Transaktion noch aussteht.
Herkömmliche asynchrone Programmierung
Microsoft.Data.SqlClient behält die Methoden BeginExecuteNonQuery, BeginExecuteReader und BeginExecuteXmlReader des asynchronen Programmiermodells (APM) aus Kompatibilitätsgründen bei. Verwende sie nicht in neuem Code.
CommandTimeout wird von diesen altmodischen asynchronen Aufrufen ignoriert und von aufgabenbasierten Methoden wie ExecuteReaderAsync berücksichtigt.
Die Legacy-Methoden erfordern keine Asynchronous Processing=true. Microsoft. Data.SqlClient 4.0 und neuere Versionen unterstützen die Asynchronous Processing Verbindungseigenschaft nicht.
Verwandte Inhalte
- Beginne mit dem synchronen Befehlsausführungs-Workflow.
- Um Zeilen zu verarbeiten, rufen Sie Daten mit SqlDataReader ab.
- Um große Werte zu übertragen, nutze SqlClient-Streaming-Unterstützung.
- Für MARS-Sitzungs- und Transaktionsverhalten siehe Multiple Active Result Sets.