© EVILEG 2015-2018
Рекомендует хостинг
TIMEWEB
31 мая 2018 г. 13: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".

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

Спасибо!

Добрый день!

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

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

Насчёт моделей, мы даже в своём проекте на работе пока не используем многопоточные реализации запросов к базе данных...
Хотя возможно это связано с тем, что мы используем Wt::dbo для работы с базой данных, возможно, что эта ORM работает более быстро, хотя конечно подвисания интерфейса в некоторых случаях тоже есть, но в нашем случае это не критично.
  • #
  • отредактировано 1 июня 2018 г. 19:14
  • 1 июня 2018 г. 19: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 я использую только в том же потоке где и создаю модели.
 
Да, мьютексы я думаю решили бы проблему. Но их использование кажется мне не очень уместным, это все же БД, я думаю она должна поддерживать несколько подключений.

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

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

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

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

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

  • EVILEG
  • #
  • отредактировано 2 июня 2018 г. 10:11
  • 2 июня 2018 г. 10:07

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


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

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

Еще для меня не очевиден момент создания модели, почему мы указываем запрос, но не указываем бд и подключение? Что если баз данных несколько? У приложения где то внутри есть пул подключений и баз данных? Может в этом и есть проблема что модель и ядро используют одно подключение?

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


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

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

По факту работа с базой данных в Qt не самая сильная, ОРМ например нет вовсе, только модели данных, лишь бы было и сырые запросы.

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

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


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

Например, этот сайт на данный момент использует одновременно три своих инстанса для обслуживания пользователей и соответсвенно три коннекта к базе данных.

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

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

  • EVILEG
  • #
  • отредактировано 4 июня 2018 г. 12:40
  • 4 июня 2018 г. 12:40

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


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

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

Ответы

Только авторизованные пользователи могут отвечать на форуме.
Пожалуйста, Авторизуйтесь или Зарегистрируйтесь
25 июня 2018 г. 11:55
lebendig

C++ - Тест 006. Перечисления

  • Результат 100 баллов
  • Очки рейтинга 10
25 июня 2018 г. 11:24
lebendig

C++ - Тест 005. Структуры и Классы

  • Результат 100 баллов
  • Очки рейтинга 10
25 июня 2018 г. 8:48
lebendig

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат 100 баллов
  • Очки рейтинга 10
Последние комментарии
25 июня 2018 г. 9:34
EVILEG

Как написать игру на Qt - Урок 4. Враг - смысл в выживании

Там неправильный подсчёт был по направлению и полный бардак был с поведением, эти 45 градусов исправляли ситуацию. Точную причину уже совсем не помню. А вообще все эти расчёты довольно...
25 июня 2018 г. 9:13
MarkSD

Как написать игру на Qt - Урок 4. Враг - смысл в выживании

Здравствуйте, Подскажите, пжлст, как работает этот код : QLineF lineToTarget(QPointF(0, 0), mapFromItem(target, 0, 0));  // Проводим линию от паука к мухе qreal angl...
25 июня 2018 г. 7:51
EVILEG

PyQt5 - Урок 003. QSystemTrayIcon - Как свернуть приложение в трей

Если не ошибаюсь, можно просто удалить вот эту строку central_widget.setLayout(grid_layout) Там указатель на парента передаётся в само размещение, что автоматически заменяет в размещен...
Сейчас обсуждают на форуме
25 июня 2018 г. 17:38
IscanderChe

Иконка исполняемого файла

Спасибо!
25 июня 2018 г. 13:12
Arrow

QComboBox и База данных

И если можно еще один вопрос. Таблицы во вложении. Если писать: mainModel = new QSqlRelationalTableModel(this);mainModel-&g...;
25 июня 2018 г. 7:49
EVILEG

На чём сделан этот сайт?

Добрый день! На сервере сайта установлена Ubuntu 16.04. В качестве сервера используется VDS. Хостинг-провайдер Timeweb . Сайт написан на Django/Python, для...
19 июня 2018 г. 7:56
EVILEG

как редактировать порядок обхода этементов по нажатию TAB в Qt5 qml

Что-то наподобие такого TextField { Keys.onReturnPressed: nextItemInFocusChain().forceActiveFocus()}

Рекомендуемые страницы