A
AlexTeos31 мая 2018 г. 9:10

Использование QSqlQueryModel и базы данных в разных потоках.

Qt, QML, SQLite, QSqlQueryModel

QObject::connect: Cannot queue arguments of type 'QQmlChangeSet'Добрый день!


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

Теперь о проблеме с которой я столкнулся. Я реализую приложение где основная логика заложена в "ядро", а GUI в двух версиях на QML и на виджетах подключается к ядру с помощью представителя(MVP). Ядро переносится представителем в отдельный поток. У ядра есть модуль подключения к базе данных, соответственно с GUI он находится в разных потоках. И проблема с которой я столкнулся - в каком из потоков должны работать модели?

Изначально я создавал модели в потоке GUI, и при одновременном заполнении БД и попытке работать с ней из GUI постоянно сыпались ошибки. Перенеся создание моделей в модуль подключения к БД версия на виджетах стала работать стабильно, но версия для QML стала работать не корректно - появлялась ошибка QObject::connect: Cannot queue arguments of type 'QQmlChangeSet'
с которой я не первый кто сталкивается, но ее причины я не понял. Возникает она при вызове метода setQuery,  и гробит элементы ListView.

Для формирования "таблицы" из QSqlQueryModel - использую код отсюда - https://wiki.qt.io/How_to_Use_a_QSqlQueryModel_in_QML из раздела "A more generic approach".

Был бы вам очень благодарен, что бы вы возможно не просто подсказали мне решение этой проблемы, но и в целом описали метод использования моделей в многопоточном приложении.

Спасибо!
Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.

Вам это нравится? Поделитесь в социальных сетях!

11
Evgenii Legotckoi
  • 1 июня 2018 г. 14:37

Добрый день!

Спасибо за ваш отзыв, очень приятно.

А теперь к делу.
Вы не могли бы показать ключевые моменты вашего кода.
Как переносится ядро в отдельный поток, а также, что находится в методе setQuery.
Скорее всего не хватает мьютексов.

Насчёт моделей, мы даже в своём проекте на работе пока не используем многопоточные реализации запросов к базе данных...
Хотя возможно это связано с тем, что мы используем Wt::dbo для работы с базой данных, возможно, что эта ORM работает более быстро, хотя конечно подвисания интерфейса в некоторых случаях тоже есть, но в нашем случае это не критично.
    A
    • 1 июня 2018 г. 15:12
    • (ред.)
    Рассмотрим QML версию. Представитель переносит ядро в отдельный поток:
     
    core->moveToThread(&core_thread);
    connect(&core_thread, SIGNAL(started()), core, SLOT(start()));
    core_thread.start();
     
    Затем в методе start происходит подключение к бд:
     
    QSqlDatabase::addDatabase("QSQLITE");
     
    Далее модели создаются либо тут же(в потоке ядра и бд) и сигналом передаются представителю который устанавливает их в контекст:
     
    context->setContextProperty("model", model);
     
    либо создаются в представителе(и находятся в потоке GUI) и так же устанавливаются в контекст. Затем уже интерфейс обновляет модели меняя параметры в запросах(с помощью setQuery), а ядро в свою очередь наполняет БД. 
     
    setQuery (код который я приводил по ссылке выше):
    void SqlQueryModel::setQuery(const QSqlQuery & query)
    {
        QSqlQueryModel::setQuery(query);
        generateRoleNames();
    }
    void SqlQueryModel::generateRoleNames()
    {
        m_roleNames.clear();
        for( int i = 0; i < record().count(); i ++) {
            m_roleNames.insert(Qt::UserRole + i + 1, record().fieldName(i).toUtf8());
        }
    }
    
    Причем setQuery я использую только в том же потоке где и создаю модели.
     
    Да, мьютексы я думаю решили бы проблему. Но их использование кажется мне не очень уместным, это все же БД, я думаю она должна поддерживать несколько подключений.
      Evgenii Legotckoi
      • 1 июня 2018 г. 18:55

      с мьютексами вопрос не в том, что БД не справится, это понятно, что БД должна это разрулить у себя.

      Мьютексы я рекомендую, чтобы не свернуть шею ядру приложения, когда у моделей выполняется запрос.
      Я даже не уверен, с какой стороны к этому стоит подойти.

      Здесь очень скользкий момент получается с данными и моделями в рамках QML.

      Если у модели не устанавливается parent в C++, то тогда parent устанавливается в QML контексте, и тогда модель принадлежит сборщику мусора. Подробнее в этой статье . А когда модель оказывается в QML, то фактически она оказывается в GUI потоке и уже то, что она была создана в ином потоке уже не хорошо.

      Я бы попробовал делать сырой запрос через QSqlQuery, формировать результат и уже это добро устанавливать в качестве вектора объектов в модель. Передавать объекты при этом через сигнал-слотовое соединение и обязательно использовать мьютексы, чтобы не угробить данные.

        A
        • 2 июня 2018 г. 6:07
        • (ред.)

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


        Дело в том, что я вообще не до конца понимаю почему возникла эта ситуация. Мне кажется, использование модели и ее представления в разных потоках очевидна, и бд должна ее поддерживать в обязательном порядке (целостность транзакций и пр.), ведь к бд могут одновременно подключаться не только разные потоки но и разные приложения и даже с разных физических устройств. 

        Как ядро может свернуть себе шею? Оно добавляет запись, бд производит транзакцию. Даже если в этот же момент модель производит обновление, она делает свою транзакцию, и уже БД своими светофорами контролирует что бы они друг другу не помешали.

        Еще для меня не очевиден момент создания модели, почему мы указываем запрос, но не указываем бд и подключение? Что если баз данных несколько? У приложения где то внутри есть пул подключений и баз данных? Может в этом и есть проблема что модель и ядро используют одно подключение?
          Evgenii Legotckoi
          • 4 июня 2018 г. 3:32

          QSqlQueryModel в принципе при каждом обновлении тащит заново все записи... так что в любом случае большой объём данных пересылается.
          Там разве что можно оптимизировать запросы и догружать данные при сортировки используя информацию и о первом и последнем запросе в выборке... В общем здесь большоё поле жкспериментов для раздумий...


          БД-то это поддерживает, это понятно, с этим я и не спорю, тут вопрос в том, что если одновременно будет два select в одну модель данных, то она может потерять часть данных, если запросы к ней идут из разных потоков и не защищаются мьютексами. Поэтому я и говорю что ядро программы может свернуть себе шею...

          А насчёт подключения БД, то когда вы задаёте подключение к базе данных, например, как в классе DataBase в этой статье (Метод openDataBase), то информация о подключении сохраняется в статическом контейнере (уже не помню, что там именнно вектор или map, какой-нибудь) в недрах модуля Qt SQL. Поэтому все эти запросы QSqlQuery и работают нормально, во всяком случае с одним подключением к одной базе данных проблем не возникает.
          Это всё глубоко зарыто в pimpl QSqlDataBase, если скачаете исходники Qt, то можете посмотреть насколько глубоко это там хранится.

          По факту работа с базой данных в Qt не самая сильная, ОРМ например нет вовсе, только модели данных, лишь бы было и сырые запросы.
            A
            • 4 июня 2018 г. 8:15

            Вроде как удалось найти решение. Судя по всему проблема была именно в одном подключении. Сейчас я создаю модели в GUI потоке и для них создаю отдельное подключение через addDatabase с указанием имени подключения. В результате приложение работает стабильно, и как косвенный признак - при обновлении модели с большим количеством записей ядру временно не удается вставить новую запись в БД.

              Evgenii Legotckoi
              • 4 июня 2018 г. 8:26

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


              Я бы попробовал создать второе соединение в с базой данных для инсертов. Как вариант. Базы данных не самая сильная моя профессиональная сторона, но думаю, что это правильные мысли.

              Например, этот сайт на данный момент использует одновременно три своих инстанса для обслуживания пользователей и соответсвенно три коннекта к базе данных.
                A
                • 4 июня 2018 г. 8:30

                Да, я именно так и сделал, два подключения: одно для ядра, другое для GUI. Раньше два потока лезли в одно подключение и все ломали, а сейчас встают в очередь и ждут когда БД разблокируется.

                  Evgenii Legotckoi
                  • 4 июня 2018 г. 8:32

                  Вот поэтому я и говорил про мьютексы. Думаю, что при одном соединении можно было бы нивелировать мьтексами часть таких проблем в многопоточном вызове запросов от приложения. То есть разрулить гонку запросов ещё на уровне приложения.

                    A
                    • 4 июня 2018 г. 8:40
                    • (ред.)

                    Ну 2 подключения все таки логически оправданы. 2 отдельных компонента используют одну БД по своему усмотрению, одновременное использование БД контролируется драйвером.


                    Мьютекс же пришлось бы прокидывать через все приложение для GUI, и дважды перепроверять везде ли его использовали, ну и производительность будет точно не лучше отдельных подключений.
                      Evgenii Legotckoi
                      • 4 июня 2018 г. 8:43

                      Да, согласен с Вами

                        Комментарии

                        Только авторизованные пользователи могут публиковать комментарии.
                        Пожалуйста, авторизуйтесь или зарегистрируйтесь
                        e
                        • ehot
                        • 31 марта 2024 г. 11:29

                        C++ - Тест 003. Условия и циклы

                        • Результат:78баллов,
                        • Очки рейтинга2
                        B

                        C++ - Тест 002. Константы

                        • Результат:16баллов,
                        • Очки рейтинга-10
                        B

                        C++ - Тест 001. Первая программа и типы данных

                        • Результат:46баллов,
                        • Очки рейтинга-6
                        Последние комментарии
                        k
                        kmssr8 февраля 2024 г. 15:43
                        Qt Linux - Урок 001. Автозапуск Qt приложения под Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
                        АК
                        Анатолий Кононенко4 февраля 2024 г. 22:50
                        Qt WinAPI - Урок 007. Работаем с ICMP Ping в Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
                        EVA
                        EVA25 декабря 2023 г. 7:30
                        Boost - статическая линковка в CMake проекте под Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
                        J
                        JonnyJo25 декабря 2023 г. 5:38
                        Boost - статическая линковка в CMake проекте под Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
                        G
                        Gvozdik18 декабря 2023 г. 18:01
                        Qt/C++ - Урок 056. Подключение библиотеки Boost в Qt для компиляторов MinGW и MSVC Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
                        Сейчас обсуждают на форуме
                        a
                        a_vlasov14 апреля 2024 г. 3:41
                        Мобильное приложение на C++Qt и бэкенд к нему на Django Rest Framework Евгений, добрый день! Такой вопрос. Верно ли следующее утверждение: Любое Android-приложение, написанное на Java/Kotlin чисто теоретически (пусть и с большими трудностями) можно написать и на C+…
                        Павел Дорофеев
                        Павел Дорофеев13 апреля 2024 г. 23:35
                        QTableWidget с 2 заголовками Вот тут есть кастомный QTableView с многорядностью проект поддерживается, обращайтесь
                        f
                        fastrex4 апреля 2024 г. 1:47
                        Вернуть старое поведение QComboBox, не менять индекс при resetModel Добрый день! У нас много проектов в которых используется QComboBox, в версии 5.5.1, когда модель испускает сигнал resetModel, currentIndex не менялся. В версии 5.15 при resetModel происходит try…
                        P
                        Pisych27 февраля 2023 г. 1:04
                        Как получить в массив значения из связанной модели? Спасибо, разобрался:))
                        AC
                        Alexandru Codreanu19 января 2024 г. 8:57
                        QML Обнулить значения SpinBox Доброго времени суток, не могу разобраться с обнулением значение SpinBox находящего в делегате. import QtQuickimport QtQuick.ControlsWindow { width: 640 height: 480 visible: tr…

                        Следите за нами в социальных сетях