Qt C++ - QObject::startTimer: Timers cannot be started from another thread
Возвращаюсь к сортировкам и Qt. Пытаюсь реализовать некоторое подобие sound-of-sorting от panthema. У меня есть сцена QtGraphics, на которой я вывел белые прямоугольники, которые визуализируют массив

Реализовал класс, в котором будут лежать все сортировки и который будет посылать нужные мне сигналы во время работы сортировки в отдельном потоке:
class DemoSortWorker : public QObject
{
Q_OBJECT
signals:
void compared(size_t, size_t);
void accessed(size_t);
void swapped(size_t, size_t);
void finished();
public:
DemoSortWorker();
void bubble_sort(ArraySequence<int>&, int (*) (int, int), size_t);
void shaker_sort(ArraySequence<int>&, int (*) (int, int), size_t);
};
Сортировка:
void DemoSortWorker::bubble_sort(ArraySequence<int>& sq, int (*cmp) (int, int), size_t speed) {
emit accessed(1);
for (size_t i = sq.length() - 1; i > 0; --i) {
for (size_t j = 0; j != i; ++j) {
emit compared(j, j + 1);
thread()->msleep(10000/speed);
emit accessed(2);
if (cmp(sq[j], sq[j + 1]) > 0) {
std::swap(sq[j], sq[j + 1]);
emit accessed(2);
emit swapped(j, j + 1);
thread()->msleep(10000/speed);
}
}
}
emit finished();
}
Слипы нужны для регулировки скорости сортировки ну и для того, чтобы визуализация успевала (если есть идеи по поводу того как это "сделать быстрее и лучше", то я не против) Ну и слот на клик по кнопке start:
void SortVisualization::on_runButton_clicked()
{
ui->runButton->setEnabled(false);
ui->speedSlider->setEnabled(false);
ui->sizeSlider->setEnabled(false);
ui->sortingsListWidget->setEnabled(false);
ui->typeComboBox->setEnabled(false);
thread = new QThread;
worker = new DemoSortWorker;
void (DemoSortWorker::*sort)(ArraySequence<int>&, int(*)(int, int), double);
connect(thread, &QThread::started, worker, [&]() {
if (ui->sortingsListWidget->currentItem()->text() == "Пузырьковая сортировка") worker->bubble_sort(array, [](int a, int b) {return a - b;}, ui->speedSlider->value());
/*потом допишу другие или мб придумаю как это лучше сделать*/
});
connect(worker, &DemoSortWorker::accessed, [&](size_t inc) {
QString text = ui->accessCount->text();
ui->accessCount->setText(QString::number(text.toInt() + inc));
});
connect(worker, &DemoSortWorker::compared, [&](size_t i, size_t j) {
QString text = ui->cmpCount->text();
ui->cmpCount->setText(QString::number(text.toInt() + 1));
if (compared.first != -1) {
rectangles[compared.first]->setBrush(Qt::white);
rectangles[compared.second]->setBrush(Qt::white);
}
compared.first = i;
compared.second = j;
rectangles[compared.first]->setBrush(Qt::green);
rectangles[compared.second]->setBrush(Qt::green);
});
connect(worker, &DemoSortWorker::swapped, [&](size_t i, size_t j) {
QString text = ui->swapCount->text();
ui->swapCount->setText(QString::number(text.toInt() + 1));
rectangles[i]->setRect(i*(scene->width()/array.length()), scene->height() - array[i]*(scene->height()/array.length()), scene->width()/array.length(), scene->height() + 5);
rectangles[i]->setBrush(Qt::white);
rectangles[j]->setRect(j*(scene->width()/array.length()), scene->height() - array[j]*(scene->height()/array.length()), scene->width()/array.length(), scene->height() + 5);
rectangles[j]->setBrush(Qt::white);
});
connect(worker, &DemoSortWorker::finished, thread, &QThread::quit);
connect(thread, &QThread::finished, [&](){
ui->runButton->setEnabled(true);
ui->speedSlider->setEnabled(true);
ui->sizeSlider->setEnabled(true);
ui->sortingsListWidget->setEnabled(true);
ui->typeComboBox->setEnabled(true);
thread->deleteLater();
worker->deleteLater();
});
worker->moveToThread(thread);
thread->start();
}
В чём собственно проблема:
- Если элементы, которые сейчас сравниваются успешно перекрашиваются в зелёный, как и надо, то элементы, которые свапаются просто пропадают (и дело не в том, что я делаю по-уродски через setRect)
- Причина первого скорее всего кроется в предупреждениях дебаггера:
QObject::startTimer: Timers cannot be started from another thread. Смысл предупреждения понятен, но не понятна причина.
Хочу, чтобы помогли разобраться где я налажал. Заранее благодарен.
Ответы (1 шт):
а если вместо
thread()->msleep(10000/speed)
использовать статическую
QThread::msleep(10000/speed)
или даже лучше не морозить поток, а делать сравнение элементов по таймеру (примерный код): При инициализации private переменные объекта (i,j,sq), переданные на вход и стартовать таймер
i = sq.length() - 1;
j = 0;
this->sq = sq;
conect(&timer,&QTimer::timeout(),this,&DemoSortWorker::timerTimeout);
timer.setSingleShot(false);
timer.setInterval(10000/speed);
timer.start();
и сравнивать поэлементно в слоте по таймеру
DemoSortWorker::timerTimeout(){
mutex.lock(); // или timer.stop()
if(i > 0) { i-=1;
}else{ i=sq.length() - 1;j+=1;}
if(i==j){
timer.stop();
emit finished();
}
emit compared(j, j + 1);
if (cmp(sq[j], sq[j + 1]) > 0) {
//.....
}
mutex.unlock(); // или timer.start(); тогда такты будут = заданная скорость+скорость обработки одной итерации
}