Russian Qt Forum
Ноябрь 23, 2024, 05:13 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: 1 [2] 3   Вниз
  Печать  
Автор Тема: Потокобезопасная QStandardItemModel  (Прочитано 17410 раз)
Serr500
Гость
« Ответ #15 : Июнь 08, 2012, 11:16 »

МОжет QStandardItemModel? лень смотретть в сорцы, но какой контейнер может быть в абстрактной модели?
Упс... Незамученная очепятка...  Грустный

Как вариант сделай какой-нибудь ThreadSafeTable и его помести в QAbstractItemModel
Это мысль.
Записан
Serr500
Гость
« Ответ #16 : Июнь 08, 2012, 14:48 »

Немного задолбался, но сделал. Взял QAbstractItemModel, унаследовался от неё, создал собственный контейнер и защитил все обращения к элементам контейнера в переопределённых функциях с помощью QReadWriteLock. Падать перестала. Осталось аккуратно оттестировать и убедиться, что всё нормально. Плоховато я разбираюсь в Model/View, поэтому долго возился, постоянно посматривая в справку и книжку Бланше. Код не выкладываю, поскольку он специфичен для задачи и модель не совсем полнофункциональная - только для чтения (редактировать элементы нельзя). Да ещё и куча методов, работающих со специфичными для задачи данными, хранящимися в контейнере, которые собственно и порождают содержимое модели. Когда будет время, оформлю саму модель в виде отдельного класса, а этот сделаю его наследником.
Записан
Bepec
Гость
« Ответ #17 : Июнь 09, 2012, 11:39 »

Правильное решение, я его и предлагал Подмигивающий

PS если всё грамотно защитить мутексами, то и не будет падать Подмигивающий
Записан
Serr500
Гость
« Ответ #18 : Июнь 09, 2012, 13:19 »

Не хотелось создавать собственную модель, особенно учитывая слабое знание Model/View. Со стандартной было бы намного проще. Жаль, что не получилось. Убил целый день на создание, отладку и "вылизывание" кода.

Кстати, интересный факт. Я переопределил метод rowCount. Метод защищён мьютексом. Есть ещё метод, меняющий данные, который защищён тем же мьютексом. В документации сказано, что добавление и удаление строк нужно "оборачивать" в методы beginInsertRows/endInsertRows и beginRemoveRows/endRemoveRows соответственно. Выяснилось, что begin...Rows вызывают виртуальный метод rowCount, что приводило к блокировке потока и меня вначале немного озадачило. Пришлось вместо обычного мьютекса использовать рекурсивный. Может быть, эта информация избавит кого-нибудь от долгого поиска источника блокировки.
Записан
Serr500
Гость
« Ответ #19 : Июнь 13, 2012, 08:55 »

Думал, что всё хорошо, но отладка это опровергла. Даже потокобезопасная модель не спасла от ошибок. При очень быстром изменении содержимого модели программа свалилась с ошибкой SEGFAULT. Оказалось, что вьюха (QTreeView) хранит в себе индексы (QModelIndex). В этих индексах есть internalPointer, который указывает на элемент контейнера, связанный с узлом дерева. Вьюха ухитрилась в методе QTreeView::drawTree запросить родителя у уже удалённого элемента контейнера и, естественно, свалилась. Похоже, потокобезопасность и модель несовместимы. Придётся решать задачу с помощью сигналов и слотов.
Записан
Bepec
Гость
« Ответ #20 : Июнь 14, 2012, 12:05 »

Кхм... Уважаемый. Помоему вы чтот не то творите Улыбающийся

Уже более полугода, бегает, не выключаясь, многопоточная моделька (запись телеметрии с 30+ устройств). Как бы вы что-то не то делаете, не?

Вьюха - ничего не хранит. Она всё запрашивает у модели. Если вылетает - значит вы(создатель модели), где-то передали неправильные данные, позволяющие запросить несуществующий элемент.
Записан
Serr500
Гость
« Ответ #21 : Июнь 14, 2012, 14:49 »

src\gui\itemviews\qtreeview_p.h:
Код:
struct QTreeViewItem
{
    // . . .
    QModelIndex index; // we remove items whenever the indexes are invalidated
    // . . .
};

Код:
class Q_GUI_EXPORT QTreeViewPrivate : public QAbstractItemViewPrivate
{
    // . . .
    private :
    // . . .
        mutable QVector<QTreeViewItem> viewItems
    // . . .
}

src\gui\itemviews\qtreeview.cpp:
Код:
void QTreeView::drawTree(QPainter *painter, const QRegion &region) const
{
    Q_D(const QTreeView);
    const QVector<QTreeViewItem> viewItems = d->viewItems;
    // . . .
        // paint the visible rows
        for (; i < viewItems.count() && y <= area.bottom(); ++i) {
               // . . .
                drawRow(painter, option, viewItems.at(i).index);
               // . . .
         }
    // . . .
}

Код:
void QTreeView::drawRow(QPainter *painter, const QStyleOptionViewItem &option,
                        const QModelIndex &index) const
{
    // . . .
    const QModelIndex parent = index.parent();
    // . . .
}

Вопрос. Что будет, если во время выполнения цикла в QTreeView::drawTree всё содержимое модели будет разрушено из параллельного потока?
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #22 : Июнь 14, 2012, 15:56 »

Ну а почему бы просто "не пущать" тот paint? Напр так (набросок)

Код
C++ (Qt)
void MyTreeWidget::paintEvent( ...)
{
 MyModel * m = qobject_cast <MyModel *> model();
 if (m && !m->mLocker.tryLockForRead()) {    // если модель пишется
   m->mRepaintFlag = true;   // ставим флаг перерисовки после окончания записи
   return;                              // уходим
 }
 ...
}
 
Записан
Serr500
Гость
« Ответ #23 : Июнь 15, 2012, 08:00 »

А где гарантия что виджет не полезет в модель в других местах? Фактически нужно будет создать новый виджет.

Задачу я решил слотами и сигналами. Существует потокобезопасный контейнер и отдельно от него модель. Контейнер при изменении содержимого генерирует сигналы, которые ловятся моделью. Модель - потомок QStandardItemModel и в ней хранится массив пар. Первый элемент пары - QSharedPointer на элемент контейнера, второй - QStandardItem*. Для моей задачи этого хватило. Два дня - полёт нормальный.
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #24 : Июнь 15, 2012, 09:29 »

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

Задачу я решил слотами и сигналами. Существует потокобезопасный контейнер и отдельно от него модель. Контейнер при изменении содержимого генерирует сигналы, которые ловятся моделью. Модель - потомок QStandardItemModel и в ней хранится массив пар. Первый элемент пары - QSharedPointer на элемент контейнера, второй - QStandardItem*. Для моей задачи этого хватило. Два дня - полёт нормальный.
Критиковать чужие решения легко, но все же 2 структуры для хранения одних данных не производят хорошего впечатления. Также неясно - допустим главная нитка рисует строки. Не вижу как модель может быть  безопасно изменена. Все равно надо блокировать изменения в модели при отрисовке и наоборот. Как это решает QSharedPointer ?
Записан
Serr500
Гость
« Ответ #25 : Июнь 15, 2012, 09:46 »

Нигде, нужно отловить/перекрыть все такие случаи, что может быть сложно
Вот и я о том же.

2 структуры для хранения одних данных не производят хорошего впечатления.
Хранятся не сам данные, а указатели на них. Согласен, что впечатление не очень хорошее, но иногда приходится искать компромисс между временем разработки и "элегантностью" кода. Кроме того, то что отображает модель, в явном виде в контейнере почти не хранится. Отображаемые данные строятся по данным контейнера. Проще говоря, генерируются текстовые строки из нетекстовых данных.

Также неясно - допустим главная нитка рисует строки. Не вижу как модель может быть  безопасно изменена. Все равно надо блокировать изменения в модели при отрисовке и наоборот. Как это решает QSharedPointer ?
Модель безопасно изменяется потому, что сигнал не будет обработан, пока нитка рисует строки. Может быть, я не очень понятно написал. Модель и вьюха находятся в одном потоке, поэтому метод отрисовки прерван не будет. Аналогично, обработчик сигнала не будет прерван отрисовкой.
QSharedPointer используется для того, чтобы гарантировать валидность указателя. При удалении элемента из контейнера элемент не разрушится, если он используется моделью. тем самым у нас не возникнет ситуации обращения к разрушенным данным.
Записан
Bepec
Гость
« Ответ #26 : Июнь 15, 2012, 19:33 »

Кхм. Возможно скажу очень простую вещь.

Тебе мб стоит сделать отрисовку модели как у меня - блоками? Улыбающийся

Человеческий глаз не успевает заметить изменения в 25мс. Собственно как построено.

Есть контейнер. Добавление/удаление из него идёт через мутексы из разных потоков.
Раз в 25 мс вызывается таймер и обновляет модель, используя значения предыдущего обновления и слот получения данных из контейнера с мутексом.
В результате отрисовывается не " в любой момент изменяемая модель", а только "статичное состояние модели на момент обновления".

Сумбурно наверно пояснил. Как мог Подмигивающий

Проще выражаясь - модель у тебя будет требовать данные с контейнера, только раз в 25(N) мс. Причём гарантированные данные, защищённые мутексами. Никакого своеволия не будет. Во всяком случае повторюсь - всё бачит.
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #27 : Июнь 15, 2012, 19:46 »

Есть контейнер. Добавление/удаление из него идёт через мутексы из разных потоков.
Раз в 25 мс вызывается таймер и обновляет модель, используя значения предыдущего обновления и слот получения данных из контейнера с мутексом.
В результате отрисовывается не " в любой момент изменяемая модель", а только "статичное состояние модели на момент обновления".
Если я правильно понял, модель - еще один контейнер, данные в него перегружаются из первого. Поскольку это выполняется в главной нитке - нет конфликта с рисованием. Все-таки лок надо взять и интересно - а что же будет если первый контейнер изменяется непрерывно, так сказать "мутным потоком"?

Во всяком случае повторюсь - всё бачит.
Цитировать
Тестирование может показать наличие ошибок но не их отсутствие
Улыбающийся
Записан
Serr500
Гость
« Ответ #28 : Июнь 15, 2012, 21:22 »

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

Вариант отрисовки по таймеру интересный и применяется у меня в другом проекте. Правда, там не модель, а некое, скажем так, "состояние". Вначале я обновлял GUI при каждом изменении "состояния" и получил очень интересный эффект - обработчик уже завершился, а GUI ещё несколько секунд "разгребает" кучу сигналов и перерисовывается. Прикольно, но абсолютно непрактично.  Улыбающийся Сделал обновление по таймеру раз в 250 мс - резко упала нагрузка на CPU и пропал вышеуказанный эффект.

Сумбурно наверно пояснил. Как мог Подмигивающий
Вполне понятно. Спасибо.  Подмигивающий

Если я правильно понял, модель - еще один контейнер, данные в него перегружаются из первого.
Правильно.

Все-таки лок надо взять
Не очень понял. Какой лок надо взять?

и интересно - а что же будет если первый контейнер изменяется непрерывно, так сказать "мутным потоком"?
В моей реализации будет очень худо - GUI не успеет разгрести сигналы об изменении модели. В случае Вереса всё будет нормально, просто отображение может запаздывать на время интервала таймера.
Записан
Bepec
Гость
« Ответ #29 : Июнь 15, 2012, 23:04 »

Лок - mutex.lock() Улыбающийся

Дабы не смущать другие потоками возможностью ошибок Улыбающийся Мой вариант работоспособен на 97% Веселый

1% на мои кривые руки, 1% на плохую ОС, 1% на форс мажорные обстоятельства Веселый

PS кстати на основе этого можно неплохую защиту сделать для программ - как показывает практика, попытка стороннего доступа(инжектом, али подменой функции) вся модель падает Веселый
Записан
Страниц: 1 [2] 3   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.186 секунд. Запросов: 22.