Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Utilisez des méthodes asynchrones basées sur des tâches pour éviter de bloquer un thread pendant que Microsoft. Data.SqlClient attend les E/S de connexion et réseau. L’exécution asynchrone ne rend pas une commande ou un lecteur sûr pour une utilisation simultanée, et elle ne réduit pas le temps d’exécution des requêtes sur le serveur.
Les exemples qui interrogent les tables d’application utilisent la base de données d’exemple AdventureWorks. Les exemples de C# nécessitent using Microsoft.Data.SqlClient;.
Convertir un chemin d’appel en code asynchrone
Convertissez un chemin d’appel complet à la fois :
- Rendez la méthode englobante asynchrone.
- Remplacez les appels de connexion, de commande, de lecteur et de transaction par leurs homologues basés sur les tâches.
-
awaitNettoyez après chaque opération avant d’utiliser à nouveau le même objet. - Transmettez le cancellation token de l’appelant à chaque opération annulable par l’appelant.
- Conservez un délai d’expiration de la commande limité comme limite des opérations de base de données.
Utilisez await de l’ouverture de la connexion jusqu’au traitement des résultats. L’extrait suivant suppose que connectionString et sql sont déjà définis. Elle crée un CancellationTokenSource avec un délai fini pour l’opération :
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 renvoie lorsque le premier résultat est disponible, pas après l’arrivée de toutes les lignes. Chaque appel à ReadAsync avance d’une ligne, donc la boucle traite les lignes au fur et à mesure qu’elles arrivent et ne libère le thread que pendant qu’il attend d’autres données du serveur. Le jeton de 30 secondes limite toute l’opération, y compris la boucle de lecture.
Une seule exécution asynchrone peut être en attente sur un SqlCommand. Une seule lecture asynchrone, une transition de résultat ou une opération de champ peut être en attente sur un SqlDataReader. Attendez chaque opération avant de commencer la suivante sur le même objet.
Fonctionnalités de programmation asynchrone
Les méthodes synchrones suivantes ont des équivalents basés sur des tâches qui acceptent un jeton d’annulation :
| Méthode synchrone | Méthode asynchrone | Exemple |
|---|---|---|
SqlConnection.Open |
SqlConnection.OpenAsync |
Convertir une méthode synchrone |
SqlCommand.ExecuteNonQuery |
SqlCommand.ExecuteNonQueryAsync |
Exécuter les commandes de façon asynchrone |
SqlCommand.ExecuteScalar |
SqlCommand.ExecuteScalarAsync |
Exécuter les commandes de façon asynchrone |
SqlCommand.ExecuteXmlReader |
SqlCommand.ExecuteXmlReaderAsync |
Exécuter les commandes de façon asynchrone |
SqlCommand.ExecuteReader |
SqlCommand.ExecuteReaderAsync |
Convertir un chemin d’appel en code asynchrone |
SqlDataReader.Read |
SqlDataReader.ReadAsync |
Convertir un chemin d’appel en code asynchrone |
SqlDataReader.NextResult |
SqlDataReader.NextResultAsync |
Lisez les résultats de manière asynchrone |
SqlDataReader.IsDBNull |
SqlDataReader.IsDBNullAsync |
Lisez les résultats de manière asynchrone |
SqlDataReader.GetFieldValue<T> |
SqlDataReader.GetFieldValueAsync<T> |
Lisez les résultats de manière asynchrone |
SqlBatch.ExecuteNonQuery, ExecuteScalar et ExecuteReader |
SqlBatch.ExecuteNonQueryAsync, ExecuteScalarAsync et ExecuteReaderAsync |
Exécutez un lot |
SqlBulkCopy.WriteToServer |
SqlBulkCopy.WriteToServerAsync |
Utilisez des opérations asynchrones avec SqlBulkCopy |
Utilisez la suppression synchrone lorsqu’un objet n’implémente que IDisposable. À utiliser await using pour les objets qui implémentent IAsyncDisposable. Libérez le reader avant de réutiliser sa connexion.
Exécuter les commandes de façon asynchrone
Les exemples suivants supposent que connection est ouverte. Utilisez ExecuteScalarAsync pour une seule valeur, et passez un jeton d’annulation appartenant à l’appelant :
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}");
À utiliser ExecuteNonQueryAsync pour les instructions modifiant les données. Utilisez-le ExecuteXmlReaderAsync pour une FOR XML requête. Libérez un lecteur XML avant d’exécuter une autre commande sur la même connexion. Sans Multiple Active Result Sets (MARS), une connexion ne permet qu’un seul lecteur de données ouvert.
Lisez les résultats de façon asynchrone
L’exemple suivant transmet le même jeton d’annulation à chacun de ces appels asynchrones :
-
ExecuteReaderAsyncexécute la requête. -
ReadAsynclit chaque ligne. -
GetFieldValueAsynclit la valeur de chaque colonne. -
NextResultAsyncpasse au jeu de résultats suivant. -
IsDBNullAsyncvérifie la présence deNULL.
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 passe à l’ensemble de résultats suivant.
GetFieldValueAsync et IsDBNullAsync lisent une colonne de la ligne actuelle. Lorsque vous utilisez la valeur par défaut CommandBehavior, l’enregistrement est déjà mis en tampon, donc ces appels se terminent généralement sans attendre. Quand vous utilisez CommandBehavior.SequentialAccess, elles peuvent attendre la réception des données de colonne depuis le réseau. Pour plus d’informations sur l’accès séquentiel, voir le support du streaming SqlClient.
Convertir une méthode synchrone
Cette méthode synchrone ouvre une connexion, exécute une commande, et retourne le nombre de lignes dans sys.objects:
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()!;
}
Pour convertir la méthode en code asynchrone, effectuez ces modifications :
- Changez le type de retour par
Task<int>. - Ajoutez un
CancellationTokenparamètre. - Remplacez
OpenparOpenAsync, et remplacezExecuteScalarparExecuteScalarAsync. - Libérez la connexion avec
await using.
La méthode convertie ressemble à ceci :
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))!;
}
L’appelant crée le jeton et attend le résultat :
using var cancellation = new CancellationTokenSource(TimeSpan.FromSeconds(30));
int objectCount = await CountObjectsAsync(connectionString, cancellation.Token);
Console.WriteLine($"Objects: {objectCount}");
Les deux méthodes donnent le même nombre. La sortie dépend de la base de données à laquelle vous vous connectez.
Connect Timeout limite toujours l’établissement de la connexion et les temps d’attente du pool de connexions. L’annulation du token peut redonner la main à l’appelant plus rapidement.
Ajouter des appels asynchrones à une application existante
Convertissez un chemin d’appel complet à la fois. Évitez d’appeler .Result, .Wait(), ou GetAwaiter().GetResult() sur des tâches de base de données car ces appels se bloquent et peuvent provoquer un interblocage dans des environnements avec un contexte de synchronisation.
Ne lancez pas une commande asynchrone puis n’utilisez pas la même commande ou le même lecteur de façon synchrone avant la fin de la tâche. Sans MARS, n’utilisez pas la même connexion de façon synchrone tant que la tâche n’est pas terminée.
Ajouter l’annulation et des limites de temps
Annuler une opération asynchrone
Passer le même jeton d’annulation de l’ouverture de la connexion jusqu’à l’exécution de la commande :
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);
L’annulation est effectuée au mieux. L’opération peut se terminer avant que l’annulation ne prenne effet, et l’exception observée dépend de l’endroit où l’annulation se produit. Ne supposez pas que recevoir une demande d’annulation signifie que le serveur est revenu sur le travail déjà effectué. Lorsqu’un ensemble de modifications doit réussir ou échouer en tant qu’unité, on utilise une transaction explicite. Pour plus d’informations, voir Transactions locales.
Cancel demande l’annulation de la commande en cours. Cela ne remplace pas un jeton d’annulation détenu par l’appelant. L’opération peut se terminer avant que la demande d’annulation ne prenne effet.
Après annulation ou délai d’attente, attendez la fin de l’opération et libérez le reader et le command. Inspectez l’état de la connexion avant de la réutiliser. Une défaillance rompant la connexion nécessite une nouvelle connexion.
Configurer les délais d’expiration
Les délais d’expiration de connexion et de commande couvrent différentes phases :
| Setting | Couvertures |
|---|---|
Connect Timeout |
Établissement de connexion et attente d’une connexion issue d’un pool. |
CommandTimeout |
L’exécution des commandes et les attentes cumulées de lecture réseau. |
| Jeton d’annulation | Une demande contrôlée par l’appelant pour interrompre l’attente. |
CommandTimeout se mesure en secondes.
0 signifie aucune limite de temps pour la commande. Par défaut, c’est la valeur Command Timeout de la connexion une fois configurée, sinon 30 secondes.
Pour un lecteur, le délai d’expiration de commande s’applique à l’attente réseau cumulative de chaque opération de lecture du fournisseur. Cela n’inclut pas le temps passé à traiter une ligne dans le code de l’application. Un délai d’attente peut survenir avant la première ligne ou lors de la récupération d’une ligne ultérieure.
Annulation combinée et délai d’expiration de la commande
Utilisez conjointement un délai d’attente de commande fini et une annulation par l’appelant. Cet exemple suppose que connection est ouvert :
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}");
}
Si l’appelant annule en premier, l’appel attendu déclenche une exception. Le type d’exception dépend de l’endroit où l’annulation a lieu. Si le délai d’attente de la commande expire en premier, le fournisseur signale un dépassement de délai. Dans les deux cas, la commande peut se terminer avec succès avant que l’annulation ou le délai d’attente ne prenne effet.
Modèles asynchrones avancés
Utiliser des méthodes asynchrones via les types de fournisseurs de base
Le code indépendant du fournisseur peut utiliser les méthodes asynchrones DbConnection, DbCommand et DbDataReader. Créez des paramètres via la commande fournisseur et évitez les downcasts sauf si l’application nécessite une fonctionnalité spécifique à Microsoft.Data.SqlClient.
Utiliser des méthodes de transaction SQL basées sur des tâches
Utilisez les méthodes de transaction locale basées sur des tâches lorsque le framework cible les expose. L’exemple suivant suppose que connection est ouvert, sql contient le texte de commande, et que l’appelant passe cancellationToken.
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;
}
Utilisez un jeton de nettoyage séparé pour la restauration. Le jeton de l’opération pourrait avoir déjà fait l’objet d’une annulation. Gérer un échec de l’annulation sans écarter l’exception d’opération initiale. Cet exemple préserve les deux exceptions dans une balise AggregateException. Une application peut alors enregistrer l’exception de rollback et relancer l’exception originale.
Ces méthodes Microsoft.Data.SqlClient utilisent les implémentations héritées DbConnection et DbTransaction, qui appellent les méthodes de transaction synchrones et renvoient une tâche terminée. Le fait de les attendre ne libère pas le thread d’appel pendant que l’opération de transaction s’exécute. L’annulation est vérifiée avant le début de l’opération et n’interrompt pas une opération en cours. L’exécution des commandes à l’intérieur de la transaction reste asynchrone.
Utiliser des transactions distribuées asynchrones
Des commandes asynchrones peuvent s’exécuter à l’intérieur d’un TransactionScope. Le scope définit Transaction.Current, que .NET appelle la transaction ambiante. Une connexion qui s’ouvre dans le scope s’inscrit automatiquement dans cette transaction, car le mot-clé de connexion Enlist est par défaut true. Configurez TransactionScopeAsyncFlowOption.Enabled quand la transaction doit transiter via await. Les transactions distribuées ont des exigences de plateforme, de serveur et de coordinateur. Pour plus d’informations, voir intégration System.Transactions.
Utilisez des opérations asynchrones avec SqlBulkCopy
Utilisez WriteToServerAsync pour une copie en bloc asynchrone. Gardez le lecteur source ou la collection de lignes en vie jusqu’à la fin de la tâche retournée. Configurez la taille du lot, le comportement des transactions et le délai d’expiration de l’opération. Pour plus d’informations, voir Opérations de copie en masse.
Utiliser de manière asynchrone plusieurs commandes avec MARS
MARS permet plus d’une commande en attente sur une connexion physique. Activez-le avec MultipleActiveResultSets=True uniquement lorsque l’application a besoin de ce modèle de partage de connexion.
MARS n’est pas nécessaire pour la programmation asynchrone. Sans MARS, terminez ou libérez le lecteur actif avant d’exécuter une autre commande sur la même connexion. Une seconde connexion est souvent plus claire pour un travail véritablement indépendant.
Pour le comportement des sessions et les restrictions de transaction, voir Activer plusieurs ensembles de résultats actifs.
Lire et mettre à jour des données de façon asynchrone avec MARS
Lorsque vous activez MARS, une commande peut lire les lignes tandis qu’une autre commande s’exécute sur la même connexion. Attendez chaque commande et chaque opération de lecture séparément. Les opérations MARS ne sont pas thread-safe, et l’exécution serveur peut toujours se sérialiser aux points de cession.
Lorsque les opérations de lecture et de mise à jour doivent être atomiques, assigner la même transaction locale à chaque commande et tester le comportement MARS de la transaction. Ne validez pas tant qu’une autre opération de cette transaction est encore en attente.
Programmation asynchrone héritée
Microsoft.Data.SqlClient conserve les méthodes BeginExecuteNonQuery du Modèle de Programmation Asynchrone (APM), BeginExecuteReader et BeginExecuteXmlReader pour des raisons de compatibilité. Ne les utilisez pas dans le nouveau code.
CommandTimeout est ignoré par ces appels asynchrones à l’ancienne et est honoré par des méthodes basées sur des tâches telles que ExecuteReaderAsync.
Les méthodes héritées ne nécessitent pas Asynchronous Processing=true. Microsoft. Data.SqlClient 4.0 et versions ultérieures ne prennent pas en charge la Asynchronous Processing propriété de connexion.
Contenu connexe
- Commencez par le flux de travail d’exécution synchrone des commandes.
- Pour traiter les lignes, récupérez les données avec SqlDataReader.
- Pour transférer de grandes valeurs, utilisez le support du streaming SqlClient.
- Pour le comportement des sessions et transactions MARS, voir Multiple Active Result Sets (MARS).