Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Используйте асинхронные методы, основанные на задачах, чтобы избежать блокировки потока, пока Microsoft.Data.SqlClient ожидает соединения и сетевого ввода-вывода. Асинхронное выполнение не делает команду или считыватель безопасными для одновременного использования и не сокращает время выполнения запросов на сервере.
Примеры, которые запрашивают таблицы приложения, используют образцовую базу данных AdventureWorks. Примеры C# требуют using Microsoft.Data.SqlClient;.
Преобразовать путь вызова в асинхронный код
Конвертируйте по одному полному вызову за раз:
- Сделайте внешний метод асинхронным.
- Замените вызовы соединений, команд, считывателей и транзакций на их аналоги, основанные на задачах.
-
awaitзавершайте каждую операцию перед повторным использованием того же объекта. - Передайте токен отмены вызывающего кода во всех операциях, которые он может отменить.
- Установите конечное время ожидания команды как предел времени для операций с базой данных.
Используйте await с момента открытия соединения до обработки результатов. Следующий отрывок предполагает, что connectionString и sql уже определены. Он создаёт CancellationTokenSource с конечным тайм-аутом для операции:
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 возвращает результат, когда первый результат доступен, а не после того, как все строки придут. Каждый вызов ReadAsync продвигает на одну строку, поэтому цикл обрабатывает строки по мере их поступления и не блокирует поток только в ожидании дополнительных данных от сервера. 30-секундный токен ограничивает всю операцию, включая цикл чтения.
Только одно асинхронное выполнение может ожидать для SqlCommand. Только одно асинхронное чтение, переход результата или операция поля может находиться в ожидании на SqlDataReader. Дожидайтесь завершения каждой операции перед началом следующей операции на том же объекте.
Возможности асинхронного программирования
Следующие синхронные методы имеют задачи-аналоги, принимающие токен отмены:
| Синхронный метод | Асинхронный метод | Пример |
|---|---|---|
SqlConnection.Open |
SqlConnection.OpenAsync |
Преобразовать синхронный метод |
SqlCommand.ExecuteNonQuery |
SqlCommand.ExecuteNonQueryAsync |
Запускайте команды асинхронно |
SqlCommand.ExecuteScalar |
SqlCommand.ExecuteScalarAsync |
Выполняйте команды асинхронно |
SqlCommand.ExecuteXmlReader |
SqlCommand.ExecuteXmlReaderAsync |
Выполнять команды асинхронно |
SqlCommand.ExecuteReader |
SqlCommand.ExecuteReaderAsync |
Преобразовать цепочку вызовов в асинхронный код |
SqlDataReader.Read |
SqlDataReader.ReadAsync |
Преобразовать цепочку вызовов в асинхронный код |
SqlDataReader.NextResult |
SqlDataReader.NextResultAsync |
Читайте результаты асинхронно |
SqlDataReader.IsDBNull |
SqlDataReader.IsDBNullAsync |
Читайте результаты асинхронно |
SqlDataReader.GetFieldValue<T> |
SqlDataReader.GetFieldValueAsync<T> |
Читайте результаты асинхронно |
SqlBatch.ExecuteNonQuery, ExecuteScalar и ExecuteReader |
SqlBatch.ExecuteNonQueryAsync, ExecuteScalarAsync и ExecuteReaderAsync |
Запустите пакет |
SqlBulkCopy.WriteToServer |
SqlBulkCopy.WriteToServerAsync |
Используйте асинхронные операции с SqlBulkCopy |
Используйте синхронную утилизацию, когда объект реализует только IDisposable. Используйте await using для объектов, реализующих IAsyncDisposable. Освободите объект reader перед повторным использованием его соединения.
Асинхронный запуск команд
Следующие примеры предполагают, что connection открыт. Используйте ExecuteScalarAsync в случае одного значения и передайте токен отмены, принадлежащий вызывающему:
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}");
Используйте ExecuteNonQueryAsync для операторов, изменяющих данные. Используйте ExecuteXmlReaderAsync для запроса FOR XML. Освободите XML-ридер перед выполнением другой команды на том же соединении. Без множества активных наборов результатов (MARS) соединение позволяет открыть только один объект чтения.
Читайте результаты асинхронно
Следующий пример передаёт один и тот же токен отмены каждому из этих асинхронных вызовов:
-
ExecuteReaderAsyncвыполняет запрос. -
ReadAsyncчитает каждую строку. -
GetFieldValueAsyncсчитывает каждое значение столбца. -
NextResultAsyncпереходит к следующему набору результатов. -
IsDBNullAsyncпроверяет наличиеNULL.
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 переходит к следующему набору результатов.
GetFieldValueAsync и IsDBNullAsync читают один столбец текущей строки. При использовании по умолчанию CommandBehavior ряд уже буферизован, поэтому эти вызовы обычно завершаются без ожидания. Когда вы используете CommandBehavior.SequentialAccess, они могут ожидать загрузки данных столбцов из сети. Для получения дополнительной информации о последовательном доступе см. Поддержка потоковой передачи в SqlClient.
Преобразовать синхронный метод
Этот синхронный метод открывает соединение, запускает команду и возвращает количество строк в 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()!;
}
Чтобы преобразовать метод в асинхронный код, внесите следующие изменения:
- Измените тип возврата на
Task<int>. - Добавьте
CancellationTokenпараметр. - Замените
OpenнаOpenAsync, и заменитеExecuteScalarнаExecuteScalarAsync. - Удалить соединение с
await using.
Конвертированный метод выглядит так:
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))!;
}
Вызывающая сторона создаёт токен и ждёт результата:
using var cancellation = new CancellationTokenSource(TimeSpan.FromSeconds(30));
int objectCount = await CountObjectsAsync(connectionString, cancellation.Token);
Console.WriteLine($"Objects: {objectCount}");
Оба метода возвращают одинаковое количество. Результат зависит от базы данных, к которой вы подключаетесь.
Connect Timeout по-прежнему ограничивает установление соединений и ожидание получения соединения из пула. Отмена токена может быстрее вернуть контроль вызывающему.
Добавить асинхронные вызовы в существующее приложение
Конвертируйте по одному полному маршруту вызова за раз. Избегайте вызова .Result, .Wait() или GetAwaiter().GetResult() для задач базы данных, так как эти вызовы блокируют и могут привести к взаимной блокировке в средах с контекстом синхронизации.
Не начинайте асинхронную команду, а затем синхронно используйте ту же команду или объект чтения данных до завершения задачи. Без MARS не используйте одно и то же соединение синхронно, пока задача не завершится.
Добавьте отмену и ограничения по времени
Отмена асинхронной операции
Передайте тот же токен отмены при открытии соединения и далее до выполнения команд:
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);
Отмена выполняется по возможности. Операция может завершиться до вступления в силу отмены, и наблюдаемое исключение зависит от того, где происходит отмена. Не думайте, что получение запроса на отмену означает, что сервер откатил завершённую работу. Когда набор изменений должен либо выполниться, либо завершиться ошибкой как единое целое, используйте явную транзакцию. Для получения дополнительной информации см. раздел «Локальные транзакции».
Cancel запрашивает отмену текущей команды. Он не заменяет токен отмены, принадлежащий вызывающему коду. Операция может завершиться до того, как запрос на отмену вступит в силу.
После отмены или тайм-аута дождитесь завершения операции и освободите ресурсы, связанные с reader и command. Перед повторным использованием проверьте состояние соединения. Сбой с разрывом соединения требует нового соединения.
Настройка времени ожидания
Тайм-ауты соединения и команд охватывают разные этапы:
| Setting | Обложки |
|---|---|
Connect Timeout |
Установление соединения и ожидание соединения из пула. |
CommandTimeout |
Ожидания выполнения команд и накопительного ожидания чтения из сети. |
| Маркер отмены | Запрос на прекращение ожидания, управляемый вызывающим абонентом. |
CommandTimeout измеряется в секундах.
0 означает отсутствие ограничения по времени выполнения команды. По умолчанию используется значение Command Timeout соединения, если оно настроено, иначе — 30 секунд.
Для средства чтения тайм-аут команды применяется к накопленному сетевому ожиданию каждой операции чтения провайдера. Это не учитывает время, затраченное на обработку строки в коде приложения. Тайм-аут может произойти перед первой строкой или при получении последующей строки.
Комбинируйте отмену и тайм-аут команды
Используйте конечный тайм-аут команды и отмену со стороны вызывающего кода одновременно. В этом примере предполагается, что connection открыто:
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}");
}
Если звонящий первым отменяет вызов, вызов, которого ожидают, выдает исключение. Тип исключения зависит от места отмены. Если тайм-аут команды истекает первым, провайдер сообщает о тайм-ауте. В любом случае команда может успешно завершиться до вступления в силу отмены или тайм-аута.
Продвинутые асинхронные паттерны
Используйте асинхронные методы через базовые типы провайдеров
Код, независимый от провайдера, может использовать DbConnection, DbCommand, и DbDataReader асинхронные методы. Создавайте параметры через команду провайдера и избегайте downcast, если только приложению не нужна функция, специфичная для Microsoft.Data.SqlClient.
Используйте методы SQL-транзакций, основанные на задачах
Используйте локальные методы транзакций на основе задач, когда целевой фреймворк их предоставляет. Следующий пример предполагает, что connection открыто, sql содержит командный текст, а вызывающий передаёт в 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;
}
Используйте отдельный токен очистки для отката. Возможно, токен операции уже отменён. Обрабатывайте сбой отката, не отбрасывая исходное исключение операции. В этом примере оба исключения сохраняются в элементе AggregateException. Вместо этого приложение может зарегистрировать исключение отката и заново выбросить исходное исключение.
Эти методы Microsoft.Data.SqlClient используют унаследованные реализации DbConnection и DbTransaction, которые вызывают синхронные методы транзакций и возвращают завершённую задачу. Ожидание их не освобождает вызывающий поток во время выполнения транзакционной операции. Отмена проверяется до начала операции и не прерывает текущую операцию. Выполнение команд внутри транзакции по-прежнему асинхронно.
Используйте асинхронные распределённые транзакции
Асинхронные команды могут выполняться внутри TransactionScope. Область задаёт Transaction.Current, которую .NET называет неявной транзакцией. Соединение, открывающееся внутри области, автоматически входит в эту транзакцию, потому что ключевое слово соединения Enlist по умолчанию имеет значение true. Настройте TransactionScopeAsyncFlowOption.Enabled , когда транзакция должна проходить через await. Распределённые транзакции имеют требования к платформе, серверу и координатору. Для получения дополнительной информации см. Интеграция с System.Transactions.
Используйте асинхронные операции с SqlBulkCopy
Используйте WriteToServerAsync для асинхронного массового копирования. Поддерживайте объект чтения источника или коллекцию row до завершения возвращённой задачи. Настройте размер пакета, поведение транзакций и тайм-аут для операции. Для получения дополнительной информации см. раздел «Операции массового копирования».
Асинхронное использование нескольких команд с помощью функции MARS
MARS допускает более одной ожидающей команды на одном физическом соединении. Включайте его MultipleActiveResultSets=True только тогда, когда приложению нужна модель совместного использования соединений.
MARS не требуется для асинхронного программирования. Без MARS завершите или освободите активный объект чтения перед выполнением другой команды на том же соединении. Второе подключение часто яснее для по-настоящему независимой работы.
Для поведения сессии и ограничений транзакций см. «Включить несколько активных наборов результатов».
Асинхронное чтение и обновление данных с помощью функции MARS
Когда вы включаете MARS, одна команда может читать строки, а другая выполняется на том же соединении. Ожидайте выполнение каждой команды и операции ридера по отдельности. Операции MARS не являются потокобезопасными, и выполнение на сервере всё равно может сериализоваться в точках передачи управления.
Когда операции чтения и обновления должны быть атомарными, назначьте одну и ту же локальную транзакцию каждой команде и проверьте поведение транзакции при MARS. Не выполняйте commit, пока другая операция в этой транзакции ещё ожидается.
Устаревшее асинхронное программирование
Microsoft.Data.SqlClient сохраняет методы асинхронной модели программирования (APM) BeginExecuteNonQuery, BeginExecuteReader и BeginExecuteXmlReader для совместимости. Не используйте их в новом коде.
CommandTimeout игнорируется этими старыми асинхронными вызовами и учитывается методами, основанными на задачах, такими как ExecuteReaderAsync.
Устаревшие методы не требуют Asynchronous Processing=true. Microsoft. Data.SqlClient 4.0 и более поздние версии не поддерживают Asynchronous Processing свойство подключения.
Связанные материалы
- Начните с синхронного рабочего процесса выполнения команд.
- Для обработки строк извлекайте данные с помощью SqlDataReader.
- Для передачи больших значений используйте поддержку потоковой передачи SqlClient.
- Для поведения сессий и транзакций MARS см. Multiple Active Result Sets.