Russian Qt Forum

Qt => Model-View (MV) => Тема начата: YvenTitan от Февраль 11, 2016, 15:51



Название: Частое добавление и удаление строки в QAbstractTableModel
Отправлено: YvenTitan от Февраль 11, 2016, 15:51
Добрый день!

Стоит задача сделать таблицу обновлений с помощью QAbstractTableModel.

В небольшом тестовом примере таблица из 4 строк. В нем в отдельном потоке я раз 1 милисекунду добавляю в конец таблицы одну строку с помощью insertRows и одну строку удаляю из ее начала(removeRows). В итоге через 10-60 секунд работы приложение вылетает.
Если добавлять и удалять строки раз в 10 милисекунд, то работает нормально.

Как сделать, чтобы приложение работало стабильно?


Название: Re: Частое добавление и удаление строки в QAbstractTableModel
Отправлено: Bepec от Февраль 11, 2016, 16:30
Перестать пороть чушь и сделать нормальную архитектуру приложения.


Название: Re: Частое добавление и удаление строки в QAbstractTableModel
Отправлено: YvenTitan от Февраль 11, 2016, 16:34
Можно по конкретнее, что именно исправить

Вылетает с ошибкой:
ASSERT failure in QList<T>::at "index out of range" file qlist.h

This application has requested the Runtime to terminate it in an unusual way


Название: Re: Частое добавление и удаление строки в QAbstractTableModel
Отправлено: gil9red от Февраль 11, 2016, 20:20
Можно по конкретнее, что именно исправить

Вылетает с ошибкой:
ASSERT failure in QList<T>::at "index out of range" file qlist.h

This application has requested the Runtime to terminate it in an unusual way

Выход за пределы диапазона. Вы у списка запрашиваете элемент, индекс которого нет.


Название: Re: Частое добавление и удаление строки в QAbstractTableModel
Отправлено: Bepec от Февраль 11, 2016, 20:37
Пороть чушь - про добавление и удаление строки с интервалом 1мс.
А так если у вас проблемы в КОДЕ - покажите КОД.


Название: Re: Частое добавление и удаление строки в QAbstractTableModel
Отправлено: Old от Февраль 11, 2016, 20:48
Пороть чушь - про добавление и удаление строки с интервалом 1мс.
А что в этом страшного?

А так если у вас проблемы в КОДЕ - покажите КОД.
Так у ТС проблема в коде или архитектуре?


Название: Re: Частое добавление и удаление строки в QAbstractTableModel
Отправлено: Old от Февраль 11, 2016, 21:11
Можно по конкретнее, что именно исправить
Нельзя так просто работать со списками из разных потоков. Нужно использовать средств синхронизации.


Название: Re: Частое добавление и удаление строки в QAbstractTableModel
Отправлено: Bepec от Февраль 11, 2016, 21:45
Таки не заметил прикрепленного архива, извиняюсь.

to Old: да, таки проблема в архитектуре и как следствие реализация в коде.

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


Название: Re: Частое добавление и удаление строки в QAbstractTableModel
Отправлено: Igors от Февраль 12, 2016, 07:49
Хммм... а что и как засисять? Налепить QMutexLocker во всех методах? Это выглядит уродливо но ничего не гарантирует - могут найтись неперекрытые базовые методы, а то и вообще заклинит


Название: Re: Частое добавление и удаление строки в QAbstractTableModel
Отправлено: Old от Февраль 12, 2016, 08:00
могут найтись неперекрытые базовые методы, а то и вообще заклинит
Что значит "могут найтись": небыло-небыло, а потом раз и нашлись? Нужно постараться не забыть перекрыть.
А вообще утверждать работоспособность модели в не gui-потоке я не могу, все никак руки не дойдут попробовать. Я долгие операции выполняю в рабочих нитках, и по сигналу добавляю информацию в модель уже из контексте gui-нитки.


Название: Re: Частое добавление и удаление строки в QAbstractTableModel
Отправлено: Bepec от Февраль 12, 2016, 09:00
Не найдется никаких базовых методов. Ибо контейнер добавлен исключительно ТСсом и все методы где он используются описаны опять таки в классе ТСса.

PS да, просто мутекс локеры натыкать и всё становится хорошо.


Название: Re: Частое добавление и удаление строки в QAbstractTableModel
Отправлено: Igors от Февраль 12, 2016, 09:11
Ладно, попробуем, ну не с мутексами конечно. В аттаче измененные файлы. Вроде "не летит", но увы - это еще ничего не значит.  

Не найдется...
Свободен


Название: Re: Частое добавление и удаление строки в QAbstractTableModel
Отправлено: YvenTitan от Февраль 12, 2016, 09:22
В реальной задаче добавлять и удалять строку надо чаще, там приходит много обновлений. Это связано с тем, что они приходят с датчиков. Это простой пример, чтобы немного понятно было.

Мьютексы я пробовал делать, но не помогало. Если кто конкретно скажет, куда их поставить, то буду благодарен. В данном тестовом примере поможет qSleep(1) после beginInsertRows и removeInsertRows, но в реальном примере это тоже бессильно.

Мне кажется, что проблема в следующем. При вызове функции beginInsertRows испускается сигнал rowsAboutToBeInserted(), по нему объект QTableView выполняет в потоке вставку строк в себя. При endInsertRows испускается rowsInserted. Аналогично при вызове beginRemoveRows выделяется сигнал rowsAboutToBeRemoved и в потоке QTableView удаляет из View строку. Как синхронизировать эти потоки - для меня главный вопрос. Если где-то неправ - поправьте.


Название: Re: Частое добавление и удаление строки в QAbstractTableModel
Отправлено: Bepec от Февраль 12, 2016, 09:32
Система сигнал слотов сама разбирает проблему с потоками, хотя в вашем случае хзкак оно работает :D

Если поставить QMutexLocker в функции data, insertRows, removeRows, то в данном конкретном проекте вылеты прекращаются.

to Igors: пора бы знать, что если никто не знает об объекте, то и обращаться к нему не могёт, так что "найтись" не могут моменты. Разве что сам напишешь.


Название: Re: Частое добавление и удаление строки в QAbstractTableModel
Отправлено: Old от Февраль 12, 2016, 09:32
В реальной задаче добавлять и удалять строку надо чаще, там приходит много обновлений. Это связано с тем, что они приходят с датчиков. Это простой пример, чтобы немного понятно было.
Не очень понятно, для чего вы всю модель переместили в контекст другой нитки.
Лучше из другой нитки эмитить сигнал, когда появились данные для вставки, а слот из gui-нитки уже будет вставлять их в модель (и выкидывать устаревшие).


Название: Re: Частое добавление и удаление строки в QAbstractTableModel
Отправлено: Igors от Февраль 12, 2016, 10:03
Мне кажется, что проблема в следующем. При вызове функции beginInsertRows испускается сигнал rowsAboutToBeInserted(), по нему объект QTableView выполняет в потоке вставку строк в себя. При endInsertRows испускается rowsInserted. Аналогично при вызове beginRemoveRows выделяется сигнал rowsAboutToBeRemoved и в потоке QTableView удаляет из View строку. Как синхронизировать эти потоки - для меня главный вопрос.
Не исключено что и "никак". Напр где-то "в недрах" есть код типа
Код
C++ (Qt)
// main thread
int numRow = model->rowCount();
for (int i = 0; i < numRow; ++i) {
// do something with row[i]
}
 
Если рабочая нитка в это время меняет число строк - вылет неизбежен. Нужно залочить код выше, но это может оказаться слишком трудным или вообще недостижимым. И никто не гарантирует что такой код не появится в след версии Qt.

С др стороны если сигналы с датчиков приходят интенсивно - какой смысл дергать UI на каждый чих? Логичнее накапливать/буферировать изменения а потом их скопом применить.

Мьютексы я пробовал делать, но не помогало. Если кто конкретно скажет, куда их поставить
На paint вьюхи точно нужен. Но я бы с этим вообще не связывался - это ненадежно


Название: Re: Частое добавление и удаление строки в QAbstractTableModel
Отправлено: Bepec от Февраль 12, 2016, 10:12
to Igors:
Почитайте про Model/View.
1) Все операции с контейнером который имеется лишь в наследнике, производятся ТОЛЬКО в наследнике.
2) Все операции производимые моделью берут данные из переопределённых методов, т.е. ColumnCount/RowsCount/Data.
3) Что модель не обрабатывает данные, а лишь отдаёт данные по сигналу View.
4) Никто не гарантирует что всё не изменится, если изменится в любом случае придётся переписывать, и ваши "предположения" ни к чему не приведут.
5) UI не дёргается на каждый чих. Дёргается только модель, а View обновляться будет с нормальной частотой и по необходимости. Это опять таки написано в пунктике Model/View.

Цитировать
На paint вьюхи точно нужен
- феерический бред. Модель работает с данными. Все методы работы с контейнером переопределены правильно, и как следствие нужно ставить мутекс только на них.

PS мне вот лично интересно как paint View может повлиять на данные в модели? :P


Название: Re: Частое добавление и удаление строки в QAbstractTableModel
Отправлено: Igors от Февраль 12, 2016, 11:41
PS мне вот лично интересно как paint View может повлиять на данные в модели? :P
Еще как повлияет, правда наоборот :) Мне даже не хочется Вас пинать, все равно без толку, ничему не научитесь


Название: Re: Частое добавление и удаление строки в QAbstractTableModel
Отправлено: YvenTitan от Февраль 12, 2016, 14:40
Спасибо, Igors. Ваш код помог. Правда как только я вместо QAbstractTableModel стал использовать QSortFilterModel, то проблема вернулась, несмотря на то, что я перед функцией filterAcceptRow ставлю CScopedLock lock(m_Lock).


Название: Re: Частое добавление и удаление строки в QAbstractTableModel
Отправлено: Igors от Февраль 12, 2016, 15:05
Спасибо, Igors. Ваш код помог. Правда как только я вместо QAbstractTableModel стал использовать QSortFilterModel, то проблема вернулась, несмотря на то, что я перед функцией filterAcceptRow ставлю CScopedLock lock(m_Lock).
Простой пример тоже должен вылететь, пусть и через длительное время. Дело в том что лочить надо обоих (читающего и пишущего). Пример:
Код
C++ (Qt)
void MyDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const
{
<- пробой
...
}
Пусть пока рабочая нитка не обновляла модель. Все хорошо, пришел нормальный QModelIndex. И тут в точке "пробой" UI нитка заснула, а рабочая наоборот проснулась и грохнула строки. Затем главная просыпается, лезет model->data() по индексу, но он-то уже невалиден. Без разницы MyDelegate или стандартный Qt

Поэтому придется лочить paint целиком, sizeHint и вообще всех методы работающие с моделью в цикле. Сигналы с параметрами QModelIndex тоже станут невалидны. И даже если удастся обеспечить все локи - попадаем в др беду. Напр если удаление интенсивно - залоченному paint трудно прорваться, UI будет подвисать.

Поэтому думается что работа с моделью в др нитке - принципиально неверное решение


Название: Re: Частое добавление и удаление строки в QAbstractTableModel
Отправлено: Bepec от Февраль 12, 2016, 22:17
А теперь добавляем радость, что там когда грохнули данные стоит мутекс и он блокирует такую ситуацию :D Igors вы пытаетесь доказать, что используя защищённые мутексами функции для работы с данными можно порушить стек?

Просто надо пользоваться функциями модели, защищёнными мутексами и будет всё хорошо. Мутексы гораздо быстрее обновления ui. Хотя конечно зависит от нагрузки.

PS ну если конечно написать model->m_rowList[1], то будет вылет.


Название: Re: Частое добавление и удаление строки в QAbstractTableModel
Отправлено: Igors от Февраль 13, 2016, 07:53
А теперь добавляем радость, что там когда грохнули данные стоит мутекс и он блокирует такую ситуацию :D
Для блокировки нужно чтобы мутекс был захвачен UI ниткой еще до вызова MyDelеgate::paint. А так рабочая нитка его захватывает, удаляет строки и освобождает, оставляя UI в интересном положении.


Название: Re: Частое добавление и удаление строки в QAbstractTableModel
Отправлено: Bepec от Февраль 13, 2016, 17:11
Блокировка происходит в модели. Пока там мутекс не освобождён стоит всё. И paint вызывает data, который ставит Mutex и который отдаёт данные. В один момент времени могут быть отданы данные, или же изменены данные. А т.к. данные нетяжелые и скорость изменения данных очень высока, то пользователь не заметит даже разницы работы модели с мутексом или без.

Model и View связаны сигналами, которые сами разруливают проблемы с Queued connection. Хотя тут тоже вопрос, смотря в какой момент будут они связаны и какой тип соединения будет использован.

PS хотя конечно не стоит так делать с потоками - потоки этого не любят, ну или по крайней мере, не одобряют.


Название: Re: Частое добавление и удаление строки в QAbstractTableModel
Отправлено: Igors от Февраль 13, 2016, 17:27
Блокировка происходит в модели. Пока там мутекс не освобождён стоит всё. И paint вызывает data, который ставит Mutex и который отдаёт данные. В один момент времени могут быть отданы данные, или же изменены данные.
Это не спасает т.к. QModelIndex мог быть получен ДО изменения данных. Внимательно прочитайте объяснения в посте #19


Название: Re: Частое добавление и удаление строки в QAbstractTableModel
Отправлено: Bepec от Февраль 13, 2016, 17:39
QModelIndex это просто циферки row и column для вызова функции Data или Remove или Insert.
Ставим мутекс в Data и мы защищены от ВСЕХ читающих.
Мутексы поставленные в ф-ции запроса, добавления, изменения и удаления данных защищают данные.

В том и суть реализации model-View, все данные читающие получают из стандартных функций, которые мы можем защитить мутексами.

PS сообщение прочитал, нового не узнал. По сути всё сводится - невалидный индекс в функции data, защищённой мутексом и проверкой индекса на валидность. Т.е. мутекс не даст конфликтовать при изменениях, а проверка на валидность не допустит обращения к несуществующим данным.


Название: Re: Частое добавление и удаление строки в QAbstractTableModel
Отправлено: Igors от Февраль 13, 2016, 18:18
QModelIndex это просто циферки row и column для вызова функции Data или Remove или Insert.
Ставим мутекс в Data и мы защищены от ВСЕХ читающих.
Нет. Пусть рисуется напр 5-я строка (делегат получил QModelIndex с row = 5). Мутекс модели свободен - ведь никаких обращений к модели пока нет. Именно в этот злосчастный момент др нитка захватила мутекс, грохнула строки, освободила мутекс. И когда рисовальщик наконец полезет в модель - там 5-й строки-то уже нет (или вместо нее другая).


Название: Re: Частое добавление и удаление строки в QAbstractTableModel
Отправлено: Bepec от Февраль 13, 2016, 18:26
1) удаляется 5 строка.
2) Проходит захват мутекса делегатом для отрисовки 5 строки.
3) Проходит проверка на валидность индекса. На этой части он получает отлуп - пятой строки уже не существует и её удаление произведено корректно.
4) View получает QVariant().
5) Отрисовывает пустой поле.
6) Тут приходит сигнал об удалении в модели.
7) View удаляет ненужную строку.
8) Работа завершена корректно, пользователь увидел лишь удаление строки.


Название: Re: Частое добавление и удаление строки в QAbstractTableModel
Отправлено: Igors от Февраль 14, 2016, 10:15
3) Проходит проверка на валидность индекса. На этой части он получает отлуп - пятой строки уже не существует и её удаление произведено корректно.
Без комментариев  :)


Название: Re: Частое добавление и удаление строки в QAbstractTableModel
Отправлено: Bepec от Февраль 14, 2016, 12:15
Мм?
Вы хотите сказать, что переопределяя метод data(), проверку на валидность индекса в контейнере делать не надо? Пусть даже топорным
Код:
if (massive.size() >= index.row())
return QVariant();
Или же вы никогда не проверяете наличие яблок в пакете, беря яблоко? :D


Название: Re: Частое добавление и удаление строки в QAbstractTableModel
Отправлено: Igors от Февраль 14, 2016, 12:45
Или же вы никогда не проверяете наличие яблок в пакете, беря яблоко? :D
Я там новое голосование создал, там потренируйтесь :) Здесь уже тема себя исчерпала


Название: Re: Частое добавление и удаление строки в QAbstractTableModel
Отправлено: Bepec от Февраль 14, 2016, 13:35
Увы вы исчерпали свои отмазки в конкретно данном случае) Ну да бог с вами :D


Название: Re: Частое добавление и удаление строки в QAbstractTableModel
Отправлено: Igors от Февраль 14, 2016, 13:46
Увы вы исчерпали свои отмазки в конкретно данном случае) Ну да бог с вами :D
Напротив, я даже создал новую тему для Вашего обучения :) А здесь хороший, полезный топик, не будем его захламлять


Название: Re: Частое добавление и удаление строки в QAbstractTableModel
Отправлено: Авварон от Февраль 14, 2016, 19:02
В данном случае Igors прав, может быть случай, когда количество рядов рассинхронизуется между моделью и вью. Это как если кидать beginInsertRows() после того, как данные поменялись - попробуйте, много интересного словите.


Название: Re: Частое добавление и удаление строки в QAbstractTableModel
Отправлено: Bepec от Февраль 14, 2016, 19:30
Я специально пробовал (и неспециально) когда овладевал model-view. Стандартная связка modelView защищенная мутексами с собственным контейнером работает стабильно без всяких вылетов. Да, возможно мерцания, возможны лишние "левые" строки, но они были результатом неправильно отосланных сигналом - всем известных пар "begin insert remove update".

Собственно шаблон model-View Qt-шной полностью соответствует стандарту - model рулит данными в любой форме, view же НЕ МОЖЕТ вызвать ошибку в модели, если не пропущены элементарные проверки.


Название: Re: Частое добавление и удаление строки в QAbstractTableModel
Отправлено: YvenTitan от Февраль 15, 2016, 16:29
Может не совсем в тему.
Я попробовал отказаться от работы с моделью в другой нитке. Сделал так, что в отдельной нитке испускаются сигналы раз в миллисекунду. Результат остался тот же. Поэтому решил продолжить решение Igors. Добавил модель, которая наследуется от QSortFilterProxyModel. В итоге зависает при попытке удалить элемент, в методе removeRows, в строке m_rowList.removeAt(row). Со старой проблемой эта, скорее всего не связана.
Ошибка не такая сложная, но за день не получилось ее решить.
Если не сложно, можете подсказать, где может быть недочет?


Название: Re: Частое добавление и удаление строки в QAbstractTableModel
Отправлено: Igors от Февраль 15, 2016, 17:38
Добавил модель, которая наследуется от QSortFilterProxyModel. В итоге зависает при попытке удалить элемент, в методе removeRows, в строке m_rowList.removeAt(row).
У меня не виснет а сразу летит. Использовал предложение Верес'а - пофиг ветер, если индекс невалиден, то data() возвращает пустой QVariant  :)
Код
C++ (Qt)
if (index.row() >= 0 && index.row() < m_rowList.size())  // Вересовщина :-)
   if(role == Qt::DisplayRole){
       return QVariant(m_rowList.at(index.row()).at(index.column()));
 
Так вроде "побежало", но такое решение считаю несерьезным и безграмотным, и Вам его не советую.

Почему бы не сделать "обертку" для контейнера?  Рабочая нитка не удаляет, а увеличивает счетчик удаленных (или хранит индексы удаленных если можно удалять в любом порядке). Добавлять добавляет но rowCount возвращает старое значение. Разумеется все это тоже защищено локами. В итоге обе нитки работают с 1 контейнером, но модель какое-то время показывает его старую копию. Потом (хоть по таймеру) главная нитка посылает самой себе сигнал по которому (опять-таки под защитой лока) делается removeRows, удаленные реально удаляются, и insertRows. Все совершенно легально, и (на первый взгляд) несложно


Название: Re: Частое добавление и удаление строки в QAbstractTableModel
Отправлено: Old от Февраль 15, 2016, 17:41
Сделал так, что в отдельной нитке испускаются сигналы раз в миллисекунду. Результат остался тот же.
IMHO, лучше разобраться с этим вариантом. Выкладывайте ваш проект. 


Название: Re: Частое добавление и удаление строки в QAbstractTableModel
Отправлено: YvenTitan от Февраль 16, 2016, 11:12
Спасибо, Igors. Мне этот метод тоже помог. Пока оставлю его, хоть он и "колхозный".

Old, выкладываю свой вариант с сигналами


Название: Re: Частое добавление и удаление строки в QAbstractTableModel
Отправлено: Old от Февраль 16, 2016, 17:55
Old, выкладываю свой вариант с сигналами
Ну, вы особо ничего не изменили, по прежнему перемещаете модель в контекст другого потока...
Сделайте класс Sensor, это будет имитатор сенсора. Создайте несколько объектов этого класса, пусть они будут выполняться в отдельных потоках и генерируют сигнал newData.
Модель должна находится в контексте GUI-потока и иметь слот для модификации (добавление/удаление). Соедините сигналы сенсоров со слотом модели и все заработает.
Желательно сразу продумать систему группировки строк: добавлять строки сразу пачками. Пользовать все равно не может увидить добавление строки раз в несколько миллисекунд, для него они будут добавляться пачками.