© 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 работает более быстро, хотя конечно подвисания интерфейса в некоторых случаях тоже есть, но в нашем случае это не критично.

Для Django рекомендую VDS-хостинг TIMEWEB

Рассмотрим 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, формировать результат и уже это добро устанавливать в качестве вектора объектов в модель. Передавать объекты при этом через сигнал-слотовое соединение и обязательно использовать мьютексы, чтобы не угробить данные.

Для Django рекомендую VDS-хостинг TIMEWEB

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


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

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

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

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


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

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

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

Для Django рекомендую VDS-хостинг TIMEWEB

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

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


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

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

Для Django рекомендую VDS-хостинг TIMEWEB

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

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

Для Django рекомендую VDS-хостинг TIMEWEB

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


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

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

Для Django рекомендую VDS-хостинг TIMEWEB

Ответы

Только авторизованные пользователи могут отвечать на форуме.
Пожалуйста, Авторизуйтесь или Зарегистрируйтесь
24 сентября 2018 г. 17:42
edorofeeva

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

  • Результат 100баллов,
  • Очки рейтинга10
24 сентября 2018 г. 17:37
edorofeeva

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

  • Результат 66баллов,
  • Очки рейтинга-1
23 сентября 2018 г. 14:38
No Names

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

  • Результат 60баллов,
  • Очки рейтинга-1
Последние комментарии
25 сентября 2018 г. 15:24
pasagir

Qt/C++ - Урок 006. QSqlQueryModel - Таблицы в Qt с помощью SQL-запросов

Это запись метода которая работает параллельно с БД, данные из парсера поступают в БД и в наш метод одновременно
25 сентября 2018 г. 14:56
pasagir

Qt/C++ - Урок 006. QSqlQueryModel - Таблицы в Qt с помощью SQL-запросов

Как можно динамически отображать данные в таблице? На COM-порт непрерывно приходят данные, я их принимаю сохраняю в БД, а после остановка приема/передачи данные отображаются в таблице. В табли...
25 сентября 2018 г. 10:43
Евгений Легоцкой

Qt/C++ - Урок 017. QGraphicsScene или как работать с графикой в Qt

Прямо так не написано. Хотя соглашусь, что в качестве улучшения вызов данного метода здесь к месту.
25 сентября 2018 г. 10:37
reef425

Qt/C++ - Урок 017. QGraphicsScene или как работать с графикой в Qt

В статье написано, что таймер сработает один раз. Но это не так. Было бы хорошо добавить timer->setSingleShot(true); После инициализации таймера.
24 сентября 2018 г. 15:09
Евгений Легоцкой

Qt Linux - Урок 001. Автозапуск Qt приложения под Linux

А вот здесь у меня есть пример использования supervisor. https://evileg.com/ru/post/3/ Вся статья вам там не интересна, интересен только шаг с настройкой supervisor. Он получается ...
Сейчас обсуждают на форуме
25 сентября 2018 г. 15:57
Евгений_Канусовский@1981

Чтение файлов в python

Вот код: import sys from re import matchfrom vira import *from PyQt5 import QtCore, QtGui, QtWidgetsfrom PyQt5.QtWidgets import (QDialog, QFileDialog, QMessageBox, QLineEdit, QProgr...
25 сентября 2018 г. 13:51
DmitrySD

Трансляция видео с помощью VLC по RTP

Спасибо! Данная команда не дала результата. В итоге сделал трансляцию через ffmpeg. ffmpeg.exe -f gdigrab -framerate 30 -i desktop -vcodec libx264 -preset:v veryfast -b:v 4000k -f...
25 сентября 2018 г. 13:39
Arrow

Настройка Qt Creator для Android

Конечно отпишусь.
25 сентября 2018 г. 12:22
avovana

Автозапуск и авторестарт Qt Gui Application в Linux

Не получается... Решил пробовать скрипт, выполняемый при загрузке. В скрипте вечный цикл по старту программы.
Присоединяйтесь к нам в социальных сетях