почему MySQL Connector утверждает, что уже есть открытый DataReader, когда его нет?

#c# #mysql #.net

#c# #mysql #.net

Вопрос:

Я использую .NET Connector для доступа к базе данных MySQL из моей программы на C #. Все мои запросы выполняются с помощью MySqlCommand .BeginExecuteReader с результатами IAsyncResults, хранящимися в списке, чтобы я мог периодически проверять их и вызывать соответствующие обратные вызовы всякий раз, когда они заканчиваются, извлекая данные через MySqlCommand.EndExecuteReader. Я стараюсь никогда не держать один из этих ридеров открытым, пытаясь прочитать результаты из чего-то другого.

В основном это работает нормально. Но я обнаружил, что если я запускаю два запроса одновременно, то получаю страшное исключение MySqlException: с этим соединением уже связан открытый DataReader, который должен быть закрыт первым исключением в EndExecuteReader. И это происходит при первом вызове EndExecuteReader. Таким образом, сообщение об ошибке полно вздора; на данный момент нет другого открытого DataReader, если только соединитель каким-то образом не открыл его за кулисами без моего вызова EndExecuteReader. Так что же происходит?

Вот мой цикл обновления, включая обильное ведение журнала:

     for (int i=queries.Count-1; i>=0; i--) {
        Debug.Log("Checking query: "   queries[i].command.CommandText);
        if (!queries[i].operation.IsCompleted) continue;
        var q = queries[i];
        queries.RemoveAt(i);
        Debug.Log("Finished, opening Reader for "   q.command.CommandText);
        using (var reader = q.command.EndExecuteReader(q.operation)) {
            try {
                q.callback(reader, null);
            } catch (System.Exception ex) {
                Logging.LogError("Exception while processing: "   q.command.CommandText);
                Logging.LogError(ex.ToString());
                q.callback(null, ex.ToString());
            }
        }
        Debug.Log("And done with callback for: "   q.command.CommandText);
    }
  

И вот журнал:

Снимок экрана результирующих сообщений журнала

Как вы можете видеть, я запускаю оба запроса в быстрой последовательности. (Это первое, что делает моя программа после открытия соединения с БД, просто чтобы определить, что происходит.) Затем первый, который я проверяю, говорит, что это сделано, поэтому я вызываю EndExecuteReader на нем, и бум — он уже утверждает, что есть еще один открытый. Это происходит немедленно, еще до того, как он попадет в мой метод обратного вызова. Как это может быть?

Недопустимо ли иметь два открытых запроса одновременно, даже если я вызываю EndExecuteReader только по одному за раз?

Комментарии:

1. Пожалуйста, включите STRICT ON , это плохой код.

2. Пожалуйста, уточните.

3. var q — это объект, а не mysqlcomand мне интересно, почему это не дает вам ошибки

4. «Я понимаю , чего бояться … исключение в EndExecuteReader» В шаблоне APM (который вы используете) EndX метод генерирует любые исключения, которые произошли во время асинхронной операции, так что это вполне ожидаемо. Сообщение об исключении было истинным в момент возникновения исключения, не обязательно, когда оно было добавлено в ваш код.

5. @nbk, да, q — это объект, который содержит MySqlCommand в качестве поля.

Ответ №1:

Когда вы выполняете два запроса одновременно, у вас должно быть два объекта подключения. Почему? Каждое соединение может обрабатывать только один запрос одновременно. Похоже, что ваш код попал в какое-то состояние гонки, когда сработали некоторые из ваших параллельных запросов, а затем пара из них столкнулась и завершилась неудачей.

В любом случае ваша система будет более устойчивой в производстве, если вы сможете упростить свои последовательности запуска. На вашем месте я бы выполнял один запрос за другим, а не пытался запускать их все сразу. (Очевидно, если это вызывает реальные проблемы с производительностью, вам придется запускать их одновременно. Но делайте это простым, пока вам не понадобится, чтобы оно было сложным.)

Комментарии:

1. Ну, это, похоже, соответствует наблюдениям, но задокументировано ли это ограничение где-нибудь? Я бы спал лучше, если бы мог указать на документы, от которых зависит этот рефакторинг. Все, что я смог найти, это то, что у вас не может быть двух объектов Reader, открытых одновременно, но на данный момент я этого не делаю (несмотря на утверждение в тексте исключения).

2. Это задокументировано для MySqlConnector (альтернативный MySQL ADO.NET библиотека): mysqlconnector.net/troubleshooting/connection-reuse

3. @JoeStrout Кроме того, nuget.org/packages/MySqlConnector обеспечивает фактический асинхронный ввод-вывод через новые ExecuteReaderAsync API (которые возвращают Task и намного проще в использовании, чем Begin / EndExecuteReader ). Я бы настоятельно рекомендовал переключиться.