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();
}

В чём собственно проблема:

  1. Если элементы, которые сейчас сравниваются успешно перекрашиваются в зелёный, как и надо, то элементы, которые свапаются просто пропадают (и дело не в том, что я делаю по-уродски через setRect)
  2. Причина первого скорее всего кроется в предупреждениях дебаггера: QObject::startTimer: Timers cannot be started from another thread. Смысл предупреждения понятен, но не понятна причина.

Хочу, чтобы помогли разобраться где я налажал. Заранее благодарен.


Ответы (1 шт):

Автор решения: Sergey Tatarincev

а если вместо

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(); тогда такты будут = заданная скорость+скорость обработки одной итерации
}
→ Ссылка