CancellationToken с асинхронными методами Dapper?

Я использую Dapper 1.31 от Nuget. У меня есть этот очень простой фрагмент кода,

string connString = "";
string query = "";
int val = 0;
CancellationTokenSource tokenSource = new CancellationTokenSource();
using (IDbConnection conn = new SqlConnection(connString))
{
    conn.Open();
    val = (await conn.QueryAsync<int>(query, tokenSource.Token)).FirstOrDefault();
}

Когда я нажимаю F12 на QueryAsync, он указывает мне на

public static Task<IEnumerable<T>> QueryAsync<T>
     (
        this IDbConnection cnn, 
        string sql, 
        dynamic param = null, 
        IDbTransaction transaction = null, 
        int? commandTimeout = null, 
        CommandType? commandType = null
     );

В его подписи нет CancellationToken.

Вопросы:

  • Почему фрагмент полностью готов к сборке, предполагая, что во всем решении нет ошибки компилятора?
  • Простите меня, что я не могу проверить, действительно ли вызов tokenSource.Cancel() отменит метод, потому что я не знаю, как сгенерировать длительный SQL-запрос. Действительно ли .Cancel() отменит метод и выбросит OperationCancelledException?

Благодарю вас!


person Pedigree    schedule 28.08.2014    source источник
comment
dynamic param возьмет практически все. То, что вы делаете, похоже на передачу токена отмены в качестве параметра Console.WriteLine(string, params object[]). Тот факт, что вы можете передать это, не означает, что функция поддерживает отмену.   -  person ta.speot.is    schedule 28.08.2014


Ответы (4)


Вы передаете токен отмены в качестве объекта параметра; это не сработает.

Первые асинхронные методы в dapper не предоставляли токен отмены; когда я попытался добавить их как необязательный параметр (как отдельную перегрузку, чтобы не нарушать существующие сборки), все очень запуталось с проблемами компиляции "неоднозначного метода". Следовательно, мне пришлось выставить это через отдельный API; введите CommandDefinition:

val = (await conn.QueryAsync<int>(
    new CommandDefinition(query, cancellationToken: tokenSource.Token)
).FirstOrDefault();

Затем он передает токен отмены вниз по цепочке во все ожидаемые места; использовать это задача провайдера ADO.NET, но; кажется, что это работает в большинстве случаев. Обратите внимание, что это может привести к SqlException, а не OperationCancelledException, если операция выполняется; это снова зависит от поставщика ADO.NET, но имеет большой смысл: вы могли прервать что-то важное; это проявляется как критическая проблема с подключением.

Что касается вопросов:

Почему фрагмент полностью готов к сборке, если предположить, что во всем решении нет ошибки компилятора?

Потому что... это допустимый C#, даже если он не делает того, что вы ожидаете.

Простите меня, поскольку я не могу проверить, действительно ли вызов tokenSource.Cancel() отменит метод, потому что я не знаю, как сгенерировать длительный SQL-запрос. Будет ли .Cancel() действительно отменять метод и выбрасывать OperationCancelledException?

ADO.NET зависит от поставщика, но обычно это работает. В качестве примера «как сгенерировать длинный SQL-запрос»; команда waitfor delay на SQL-сервере здесь несколько полезна, и именно ее я использую в интеграционных тестах.

person Marc Gravell    schedule 28.08.2014
comment
Это отличный ответ от самого автора. Спасибо, Марк, за альтернативный API! Ну, я не против ловить SqlException, а не OperationCancelledException, здесь я разберусь с логикой. Спасибо еще раз! - person Pedigree; 30.08.2014
comment
@Pedigree Я думаю, я также понял, как заставить его работать правильно и в основном API. - person Marc Gravell; 30.08.2014
comment
У меня есть еще один вопрос, Марк, и я не знаю, входит ли он в область действия, почему вы установили .ConfigureAwait(false) в строке 78 файл: SqlMapperAsync.cs? Что это на самом деле значит? Спасибо. - person Pedigree; 30.08.2014
comment
@Pedigree является стандартным в коде библиотеки; это означает, что ему не нужно переходить в контекст синхронизации - он не зависит ни от чего, например, от контекста adp.net и т. д. Библиотечный код почти всегда должен его использовать; код приложения обычно должен использовать контекст синхронизации, поэтому его обычно следует опускать. На самом деле, если код библиотеки забывает это сделать, иногда это может привести к жестким взаимоблокировкам (в зависимости от того, что делает код вызывающего приложения). - person Marc Gravell; 30.08.2014
comment
Привет Марк. У меня есть еще один вопрос вдогонку. Это приемлемо? await SqlMapper.ExecuteAsync(connection, new CommandDefinition(sqlStatement, param, cancellationToken: token)); где param — это список параметров? Я не понимаю, почему вы используете object parameters = null, а не dynamic param = null в CommandDefinition. Есть ли разница? - person Pedigree; 05.09.2014
comment
@Pedigree вообще ничего; наше использование param на самом деле не использует dynamic; на уровне IL dynamic === object. Только магия компилятора делает такие вещи, как obj.Whatever(), особым образом, и мы этим не пользуемся - person Marc Gravell; 05.09.2014
comment
@Pedigree в следующем выпуске dapper cancellationToken может быть доступен в исходном API; но сегодня: это не - person Marc Gravell; 05.09.2014
comment
поэтому независимо от того, использую ли я object или dynamic, результат один и тот же. Прохладно! Будет ли он корректно бросать OperationCancelledExceptionthen? - person Pedigree; 05.09.2014
comment
@Pedigree, это вопрос к ADO.NET; как я уже упоминал, он может отображаться как SqlException, если операция выполнялась - person Marc Gravell; 05.09.2014

Вы можете исправить SqlMapper.cs в Dapper lib, добавив следующие строки:

    internal IDbCommand SetupCommand(IDbConnection cnn, Action<IDbCommand, object> paramReader)
    {
        var cmd = cnn.CreateCommand();

#if ASYNC
        // We will cancel our IDbCommand
        CancellationToken.Register(() => cmd.Cancel());
#endif

Соберите свою собственную библиотеку Dapper и наслаждайтесь :)

person rislanov    schedule 24.01.2016
comment
.. а затем поддерживать его, когда Upstream принимает коммиты с новыми функциями/исправлениями ошибок... - person Pure.Krome; 18.04.2019

попробуйте использовать SqlConnection и поймать исключение при отмене

var sqlConn = db.Database.Connection as SqlConnection;
sqlConn.Open();

_cmd = new SqlCommand(textCommand, sqlConn);
_cmd.ExecuteNonQuery();

и отменить SqlCommand

_cmd.Cancel();
person Matt    schedule 16.10.2019

Я использовал один SqlConnection для нескольких потоков. А потом, когда я изменил его так, чтобы каждый Thread создавал свой SqlConnection, ошибка исчезла.

person Tadej    schedule 06.04.2018