Сегодня я узнал еще одну потенциальную проблему, трудный путь, даже если модель была только для чтения. Я использую другой поток для изменения данных в модели (фактически, моя программа состоит из более чем 20 потоков, и все они играют хорошо), который затем обновляет таймер Qt. Это работает очень хорошо, но есть проблема, с которой я столкнулся:
Вы не можете заблокировать между rowCount
/ columnCount
и data()
.
Qt работает последовательно, что означает, что на человеческом языке он будет спрашивать «насколько вы велики», а затем спрашивать «какие данные у вас есть на этой позиции», и они могут сломаться.
Рассмотрим:
int FilesQueue::rowCount(const QModelIndex &/*parent*/) const
{
std::lock_guard<decltype(mainQueueMutex)> lg(mainQueueMutex);
return filesQueue.size();
}
QVariant FilesQueue::data(const QModelIndex &index, int role) const
{
std::lock_guard<decltype(mainQueueMutex)> lg(mainQueueMutex);
if ( role == Qt::DisplayRole) {
return filesQueue[index.row()]->getFilename();
}
}
Qt будет делать звонки так:
//...
obj->rowCount();
obj->data(...);
//...
И повсеместно произошел сбой утверждения, потому что просто между rowCount()
и data()
был поток, который изменял размер данных! Это сломало программу. Итак, это происходило:
//...
obj->rowCount();
//another thread: filesQueue.erase(...)
obj->data(...);
//...
Мое решение проблемы - снова проверить размер в методе data ():
QVariant FilesQueue::data(const QModelIndex &index, int role) const
{
std::lock_guard<decltype(mainQueueMutex)> lg(mainQueueMutex);
//solution is here:
if(static_cast<int>(filesQueue.size()) <= index.row())
return QVariant();
if ( role == Qt::DisplayRole) {
return filesQueue[index.row()]->getFilename();
}
}
и прошло 3 часа моей жизни, я никогда не вернусь :-)