qual é a maneira correta de implementar um QThread… (exemplo, por favor…)

A documentação do Qt para QThread diz para criar uma class de QThread e implementar o método de execução.

Abaixo é retirado da documentação 4.7 Qthread …

Para criar seus próprios threads, subclass QThread e reimplique run (). Por exemplo:

class MyThread : public QThread { public: void run(); }; void MyThread::run() { QTcpSocket socket; // connect QTcpSocket's signals somewhere meaningful ... socket.connectToHost(hostName, portNumber); exec(); } 

Então, em cada thread que eu criei, eu fiz exatamente isso e para a maioria das coisas funciona muito bem (eu não implemento moveToThread (this) em nenhum dos meus objects e funciona muito bem).

Eu bati um problema na semana passada (consegui passar por isso trabalhando em torno de onde eu criei meus objects) e encontrei o seguinte post no blog . Aqui é basicamente diz que a subclass QThread realmente não é a maneira correta de fazê-lo (e que a documentação está incorreta).

Isso vem de um desenvolvedor do Qt, então, à primeira vista, eu estava interessado e, depois de refletir mais, concordei com ele. Seguindo os princípios OO, você realmente quer apenas subclass uma class para melhorar ainda mais essa class … não apenas para usar os methods de classs diretamente … é por isso que você instancia …

Vamos dizer que eu queria mover uma class QObject personalizada para um segmento … qual seria a maneira “correta” de fazer isso? Nesse post do blog, ele ‘diz’ ele tem um exemplo em algum lugar … mas se alguém puder explicar melhor para mim, seria muito apreciado!

Atualizar:

Como essa questão recebe tanta atenção, aqui está um copiar e colar da documentação do 4.8 com a maneira ‘apropriada’ de implementar um QThread.

 class Worker : public QObject { Q_OBJECT QThread workerThread; public slots: void doWork(const QString &parameter) { // ... emit resultReady(result); } signals: void resultReady(const QString &result); }; class Controller : public QObject { Q_OBJECT QThread workerThread; public: Controller() { Worker *worker = new Worker; worker->moveToThread(&workerThread); connect(workerThread, SIGNAL(finished()), worker, SLOT(deleteLater())); connect(this, SIGNAL(operate(QString)), worker, SLOT(doWork(QString))); connect(worker, SIGNAL(resultReady(QString)), this, SLOT(handleResults(QString))); workerThread.start(); } ~Controller() { workerThread.quit(); workerThread.wait(); } public slots: void handleResults(const QString &); signals: void operate(const QString &); }; 

Eu ainda acredito que vale a pena ressaltar que eles incluem um membro extra Worker::workerThread que é desnecessário e nunca é usado em seu exemplo. Remova essa peça e é um bom exemplo de como fazer o encadeamento no Qt.

A única coisa que posso pensar em include é afirmar ainda que os QObject s têm afinidade com um único encadeamento. Este é geralmente o segmento que cria o QObject . Então, se você criar um QObject no thread principal do aplicativo e quiser usá-lo em outro thread, será necessário usar moveToThread() para alterar a afinidade.

Isso evita ter que subclass QThread e criar seus objects no método run() , mantendo assim suas coisas bem encapsuladas.

Essa postagem de blog inclui um link para um exemplo . É muito curto, mas mostra a ideia básica. Crie seus QObject s, conecte seus sinais, crie seu QThread , mova seus QObjects para o QThread e inicie o thread. Os mecanismos de sinal / slot garantirão que os limites de encadeamento sejam cruzados corretamente e com segurança.

Talvez seja necessário introduzir a synchronization se você precisar chamar methods em seu object fora desse mecanismo.

Eu sei que o Qt tem algumas outras instalações de encadeamento que vão além dos tópicos que provavelmente valem a pena conhecer, mas eu ainda tenho que fazer isso 🙂

Aqui está um exemplo de como usar o QThread corretamente , mas tem alguns problemas com isso, que são refletidos nos comentários. Em particular, como a ordem na qual os slots são executados não é estritamente definida, isso pode levar a vários problemas. O comentário postado em 6 de agosto de 2013 dá uma boa ideia de como lidar com esse problema. Eu uso algo parecido no meu programa, e aqui está um código de exemplo para esclarecer.

A idéia básica é a mesma: eu crio uma instância QThread que mora no meu thread principal, uma instância de class de trabalho que mora no novo thread que criei, e conecto todos os sinais.

 void ChildProcesses::start() { QThread *childrenWatcherThread = new QThread(); ChildrenWatcher *childrenWatcher = new ChildrenWatcher(); childrenWatcher->moveToThread(childrenWatcherThread); // These three signals carry the "outcome" of the worker job. connect(childrenWatcher, SIGNAL(exited(int, int)), SLOT(onChildExited(int, int))); connect(childrenWatcher, SIGNAL(signalled(int, int)), SLOT(onChildSignalled(int, int))); connect(childrenWatcher, SIGNAL(stateChanged(int)), SLOT(onChildStateChanged(int))); // Make the watcher watch when the thread starts: connect(childrenWatcherThread, SIGNAL(started()), childrenWatcher, SLOT(watch())); // Make the watcher set its 'stop' flag when we're done. // This is performed while the watch() method is still running, // so we need to execute it concurrently from this thread, // hence the Qt::DirectConnection. The stop() method is thread-safe // (uses a mutex to set the flag). connect(this, SIGNAL(stopped()), childrenWatcher, SLOT(stop()), Qt::DirectConnection); // Make the thread quit when the watcher self-destructs: connect(childrenWatcher, SIGNAL(destroyed()), childrenWatcherThread, SLOT(quit())); // Make the thread self-destruct when it finishes, // or rather, make the main thread delete it: connect(childrenWatcherThread, SIGNAL(finished()), childrenWatcherThread, SLOT(deleteLater())); childrenWatcherThread->start(); } 

Algum fundo:

A class ChildProcesses é um gerenciador de processo filho que inicia novos processos filhos com chamadas spawn (), mantém a lista dos processos atualmente em execução e assim por diante. No entanto, ele precisa manter o controle dos estados filhos, o que significa usar waitpid () no Linux ou WaitForMultipleObjects no Windows. Eu costumava chamá-los em modo não-bloqueador usando um timer, mas agora eu quero mais reação imediata, o que significa modo de bloqueio. É aí que o segmento entra.

A class ChildrenWatcher é definida da seguinte forma:

 class ChildrenWatcher: public QObject { Q_OBJECT private: QMutex mutex; bool stopped; bool isStopped(); public: ChildrenWatcher(); public slots: /// This is the method which runs in the thread. void watch(); /// Sets the stop flag. void stop(); signals: /// A child process exited normally. void exited(int ospid, int code); /// A child process crashed (Unix only). void signalled(int ospid, int signal); /// Something happened to a child (Unix only). void stateChanged(int ospid); }; 

Aqui como funciona. Quando tudo isso é iniciado, o método ChildProcess :: start () é chamado (veja acima). Cria um novo QThread e um novo ChildrenWatcher, que depois é movido para o novo thread. Então eu conecto três sinais que informam ao meu gerente sobre o destino de seus processos filhos (exited / signaled / god-knows-what-happened). Então começa a diversão principal.

Eu conecto QThread :: started () ao método ChildrenWatcher :: watch () para que ele seja iniciado assim que o thread estiver pronto. Como o observador mora no novo thread, é onde o método watch () é executado (a conexão em fila é usada para chamar o slot).

Em seguida, conecto o sinal ChildProcesses :: stopped () ao slot ChildrenWatcher :: stop () usando Qt :: DirectConnection porque preciso fazê-lo de forma assíncrona. Isso é necessário para que meu thread pare quando o gerenciador ChildProcesses não for mais necessário. O método stop () se parece com isto:

 void ChildrenWatcher::stop() { mutex.lock(); stopped = true; mutex.unlock(); } 

E então ChildrenWatcher :: watch ():

 void ChildrenWatcher::watch() { while (!isStopped()) { // Blocking waitpid() call here. // Maybe emit one of the three informational signals here too. } // Self-destruct now! deleteLater(); } 

Ah, e o método isStopped () é apenas uma maneira conveniente de usar um mutex na condição while ():

 bool ChildrenWatcher::isStopped() { bool stopped; mutex.lock(); stopped = this->stopped; mutex.unlock(); return stopped; } 

Então, o que acontece aqui é que eu defino o sinalizador parado quando preciso terminar e, da próxima vez que isStopped () é chamado, ele retorna false e o encadeamento termina.

Então o que acontece quando o loop watch () termina? Ele chama deleteLater () para que o object se autodestrua assim que o controle é retornado para o loop de events do encadeamento, o que acontece logo após a chamada deleteLater () (quando watch () retorna). Voltando a ChildProcesses :: start (), você pode ver que existe uma conexão do sinal destroy () do watcher para o slot quit () do thread. Isso significa que o encadeamento termina automaticamente quando o observador é concluído. E quando terminar, ele se auto-destrói também porque o seu próprio sinal concluído () está conectado ao seu slot deleteLater ().

Esta é praticamente a mesma idéia que Maya postou, mas como eu uso o idioma de autodestruição, não preciso depender da sequência na qual os slots são chamados. É sempre auto-destrutivo primeiro, interrompe o encadeamento mais tarde e depois se autodestrói também. Eu poderia definir um sinal finished () no worker e depois conectá-lo ao seu próprio deleteLater (), mas isso significaria apenas uma conexão a mais. Como não preciso de um sinal concluído () para qualquer outra finalidade, escolhi apenas chamar deleteLater () do próprio worker.

Maya também menciona que você não deveria alocar novos QObjects no construtor do trabalhador porque eles não viveriam no thread para o qual você moveu o trabalhador. Eu diria que é assim mesmo porque é assim que a OOP funciona. Apenas certifique-se de que todos os QObjects são filhos do worker (isto é, use o construtor QObject (QObject *)) – moveToThread () move todos os filhos junto com o object sendo movido. Se você realmente precisa ter QObjects que não são filhos de seu object, então, sobrescreva moveToThread () em seu worker para que ele também mova todas as coisas necessárias.

Não para diminuir a excelente resposta de @sergey-tachenov, mas no Qt5 você pode parar de usar SIGNAL e SLOT, simplificar seu código e ter a vantagem de verificar o tempo de compilation:

 void ChildProcesses::start() { QThread *childrenWatcherThread = new QThread(); ChildrenWatcher *childrenWatcher = new ChildrenWatcher(); childrenWatcher->moveToThread(childrenWatcherThread); // These three signals carry the "outcome" of the worker job. connect(childrenWatcher, ChildrenWatcher::exited, ChildProcesses::onChildExited); connect(childrenWatcher, ChildrenWatcher::signalled, ChildProcesses::onChildSignalled); connect(childrenWatcher, ChildrenWatcher::stateChanged, ChildProcesses::onChildStateChanged); // Make the watcher watch when the thread starts: connect(childrenWatcherThread, QThread::started, childrenWatcher, ChildrenWatcher::watch); // Make the watcher set its 'stop' flag when we're done. // This is performed while the watch() method is still running, // so we need to execute it concurrently from this thread, // hence the Qt::DirectConnection. The stop() method is thread-safe // (uses a mutex to set the flag). connect(this, ChildProcesses::stopped, childrenWatcher, ChildrenWatcher::stop, Qt::DirectConnection); // Make the thread quit when the watcher self-destructs: connect(childrenWatcher, ChildrenWatcher::destroyed, childrenWatcherThread, QThread::quit); // Make the thread self-destruct when it finishes, // or rather, make the main thread delete it: connect(childrenWatcherThread, QThread::finished, childrenWatcherThread, QThread::deleteLater); childrenWatcherThread->start(); } 

subclassing a class qthread ainda irá executar o código no thread de origem. Eu queria executar um ouvinte udp no aplicativo que já está usando o Thread da GUI (o thread principal) e enquanto meu ouvinte do udp estava funcionando perfeitamente, minha GUI estava congelada, pois estava bloqueada pelos manipuladores de evento qthread com subclass. Eu acho que g19fanatic postado está correto, mas você também precisará do segmento de trabalho para migrar com êxito o object para o novo segmento. Eu encontrei este post que descreve em detalhes do Do e Dont da segmentação em QT.

Deve ler antes de decidir subclass QThread!