How to overcome the Qthread synchronization issue here? - multithreading

I have a main thread that loads the web-page and displays it. I have another thread running, that will just print the debug message on to the console.However, I am seeing that on running the QT-Thread, the web-page is not getting loaded. I tried putting the loading of web-page on to the constructor of thread, but this is also not helping here.Here is the code.
class MyJavaScriptOperations : public QObject {
Q_OBJECT
public:
Q_INVOKABLE qint32 MultOfNumbers(int a, int b) {
qDebug() << a * b;
return (a*b);
}
};
#if 1
class MyThread : public QThread
{
Q_OBJECT
public:
MyThread();
public:
void run();
};
MyThread::MyThread()
{
qDebug()<<"Constructor called";
QWebView *view = new QWebView();
view->resize(400, 500);
view->page()->mainFrame()->addToJavaScriptWindowObject("myoperations", new MyJavaScriptOperations);
view->load(QUrl("./shreyas.html"));
view->show();
this->run();
}
void MyThread::run()
{
qDebug()<<"Thread running";
while(1)
{
qDebug()<<"Fire Callback now";
}
}
#endif
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MyThread t;
//t.run();
return a.exec();
}

Just because code is in a subclass of QThread doesn't mean that code is executed in that thread. Your main thread constructs the object, and that constructor calls run(). This means that the code of the run method is still executed in the main thread, and - as it is blocking - the line a.exec() is never called, and the main thread never gets an event loop which is required for paint events and the like.
What you need to do is start the thread and wait for run() being executed:
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// TODO: Code for your web view goes here. You will probably need to
// pass the created web view into the constructor of MyThread
MyThread t;
// start the thread - this will put an event in the main event loop
t.start();
// start the event loop - this will lead to MyThread::run() being called
return a.exec();
}
This is enough to get your example running, but you will get errors when closing the web view as your way of using threads is not the intended one: If you want to make your code stable, put the code of your run() method in a separate worker class and use a default QThread for managing that, without subclassing QThread.
I recommend reading the Qt5 Documentation on threads, which is also applicable for earlier versions of Qt.

Related

QProcess in thread works but with two type output errors

I'am learning QT multithreading.
With the following code, it works ok, but there are two type errors
The code is too simple to figure out what's going wrong.
CmdController(Controller thread) -> CMD(Ping test thread) -> QProcess(excute "ping")
main.c
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
// create Test object, and then execute “ping” cmd under windows
CmdController Test;
Test.process();
return a.exec();
}
CmdController.h
// deal with "ping" command
class Cmd : public QObject
{
Q_OBJECT
public:
explicit Cmd();
private:
QProcess CmdExe;
public slots:
void process();
void readFromStdOut();
};
// Controller class
class CmdController : public QObject
{
Q_OBJECT
public:
explicit CmdController();
~CmdController();
signals:
void process();
private:
QThread mThread;
Cmd mCmd;
};
CmdController.c
Cmd::Cmd()
{
connect(&CmdExe, SIGNAL(readyReadStandardOutput()), this, SLOT(readFromStdOut()));
}
void Cmd::process()
{
CmdExe.start("ping", {"www.baidu.com"});
}
void Cmd::readFromStdOut()
{
QByteArray resault = CmdExe.readAllStandardOutput();
qDebug() << QTextCodec::codecForName("GBK")->toUnicode(resault);
}
CmdController::CmdController()
{
mCmd.moveToThread(&mThread);
connect(this, &CmdController::process, &mCmd, &Cmd::process);
mThread.start();
}
CmdController::~CmdController()
{
mThread.exit();
mThread.wait();
}
The output:
QObject: Cannot create children for a parent that is in a different thread.
(Parent is QProcess(0x6ffdf0), parent's thread is QThread(0x1f6510), current thread is QThread(0x6ffdd0)
QObject: Cannot create children for a parent that is in a different thread.
(Parent is QProcess(0x6ffdf0), parent's thread is QThread(0x1f6510), current thread is QThread(0x6ffdd0)
QObject: Cannot create children for a parent that is in a different thread.
(Parent is QProcess(0x6ffdf0), parent's thread is QThread(0x1f6510), current thread is QThread(0x6ffdd0)
"\r\n正在 Ping www.a.shifen.com [180.101.49.42] 具有 32 字节的数据:\r\n"
"来自 180.101.49.42 的回复: 字节=32 时间=31ms TTL=53\r\n"
"来自 180.101.49.42 的回复: 字节=32 时间=31ms TTL=53\r\n"
"来自 180.101.49.42 的回复: 字节=32 时间=30ms TTL=53\r\n"
"来自 180.101.49.42 的回复: 字节=32 时间=30ms TTL=53\r\n\r\n180.101.49.42 的 Ping 统计信息:\r\n 数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),\r\n往返行程的估计时间(以毫秒为单位):\r\n 最短 = 30ms,最长 = 31ms,平均 = 30ms\r\n"
QObject::connect: Cannot queue arguments of type 'HANDLE'
(Make sure 'HANDLE' is registered using qRegisterMetaType().)
Error 1st:
1-6 lines output error, shows creating children object fails, by three times.
The ping test is ok.
so, who cause the error?
QThread(0x6ffdd0) is Cmd-object,
QThread(0x1f6510) is CmdController-object,
QProcess(0x6ffdf0) clearly is QProcess-object,
does it mean the QProcess, try to create something?
Error 2ed:
last two lines.
googled, cann't find 'HANDLE' what it is.
If if remove multithreading stuff from
CmdController(Controller thread) -> CMD(Ping test thread) -> QProcess(excute "ping")
to
CmdController(Controller thread) -> QProcess(excute "ping")
there will be no error, everything works perfectly.
I'am confused.
Thank you.
To understand the problem, one must first be clear that a QObject must be executed only in the thread to which it belongs, if another thread is executed then Qt does not guarantee that it works, and that is what has happened in this case.
Let's analyze to which thread each QObject belongs, by default QObject belongs to the thread where it was created or to the thread of its father, so when you create the object "Test" this belongs the main thread, the object "mCmd" at the beginning belongs to the main thread and then you move it to "mThread", "CmdExe" belongs to the thread where it was created, that is to say to the main thread but the "Cmd" process method is executed in the secondary thread but "CmdExe" is executed in the main thread causing the problem.
The solution is to make "CmdExe" the son of "Cmd" so that both belong to the same thread and not generate problems.
// ...
class Cmd : public QObject
{
Q_OBJECT
public:
explicit Cmd(QObject *parent=nullptr);
private:
QProcess *CmdExe;
public slots:
void process();
void readFromStdOut();
};
// ...
// ...
Cmd::Cmd(QObject *parent):QObject(parent), CmdExe(new QProcess(this))
{
connect(CmdExe, &QProcess::readyReadStandardOutput, this, &Cmd::readFromStdOut);
}
void Cmd::process()
{
CmdExe->start("ping", {"www.baidu.com"});
}
// ...

Failed to start QThread when using QNetworkAccessManager

I'm currently trying to make a software that downloads a lot of files from Google Drive. Downloading is currently not a problem.
Nevertheless, I encounter an issue when launching 500+ simultaneous downloads. I use a slightly modified version of this tutorial : https://wiki.qt.io/Download_Data_from_URL.
Here is the .h file :
class FileDownloader : public QObject
{
Q_OBJECT
public:
explicit FileDownloader(QUrl url, QObject *parent = 0, int number = 0);
QByteArray downloadedData() const;
void launchNewDownload(QUrl url);
QByteArray m_DownloadedData;
QNetworkReply* reply;
static QNetworkAccessManager *m_WebCtrl;
signals:
void downloaded();
private slots:
void fileDownloaded(QNetworkReply* pReply);
};
And here is the .cpp file :
QNetworkAccessManager* FileDownloader::m_WebCtrl = nullptr;
FileDownloader::FileDownloader(QUrl url, QObject *parent) :
QObject(parent)
{
if (m_WebCtrl == nullptr) {
m_WebCtrl = new QNetworkAccessManager(this);
}
connect(m_WebCtrl, SIGNAL (finished(QNetworkReply*)),this, SLOT (fileDownloaded(QNetworkReply*)));
launchNewDownload(url);
}
void FileDownloader::launchNewDownload(QUrl url) {
QNetworkRequest request(url);
this->reply = m_WebCtrl->get(request);
}
void FileDownloader::fileDownloaded(QNetworkReply* pReply) {
m_DownloadedData = pReply->readAll();
//emit a signal
pReply->deleteLater();
emit downloaded();
}
QByteArray FileDownloader::downloadedData() const {
return m_DownloadedData;
}
The issue is "QThread::start: Failed to create thread ()" when reaching about the 500th download. I tried to limit the number of downloads which run at the same time - but I always get the same issue. Besides, I tried to delete every downloader when finishing its task - it did nothing else than crashing the program ;)
I think that it is coming from the number of threads allowed for an only process, but I'm not able to solve it !
Does anyone have an idea that could help me ?
Thank you !
QNetworkAccessManager::finished signal is documented to be emitted whenever a pending network reply is finished.
This means that if the QNetworkAccessManager is used to run multiple requests at a time (and this is perfectly normal usage). finished signal will be emitted once for every request. Since you have a shared instance of QNetworkAccessManager between your FileDownloader objects, the finished signal gets emitted for every get call you have made. So, all the FileDownloader objects get a finished signal as soon as the first FileDownloader finishes downloading.
Instead of using QNetworkAccessManager::finished, you can use QNetworkReply::finished to avoid mixing up signals. Here is an example implementation:
#include <QtNetwork>
#include <QtWidgets>
class FileDownloader : public QObject
{
Q_OBJECT
//using constructor injection instead of a static QNetworkAccessManager pointer
//This allows to share the same QNetworkAccessManager
//object with other classes utilizing network access
QNetworkAccessManager* m_nam;
QNetworkReply* m_reply;
QByteArray m_downloadedData;
public:
explicit FileDownloader(QUrl imageUrl, QNetworkAccessManager* nam,
QObject* parent= nullptr)
:QObject(parent), m_nam(nam)
{
QNetworkRequest request(imageUrl);
m_reply = m_nam->get(request);
connect(m_reply, &QNetworkReply::finished, this, &FileDownloader::fileDownloaded);
}
~FileDownloader() = default;
QByteArray downloadedData()const{return m_downloadedData;}
signals:
void downloaded();
private slots:
void fileDownloaded(){
m_downloadedData= m_reply->readAll();
m_reply->deleteLater();
emit downloaded();
}
};
//sample usage
int main(int argc, char* argv[]){
QApplication a(argc, argv);
QNetworkAccessManager nam;
FileDownloader fileDownloader(QUrl("http://i.imgur.com/Rt8fqpt.png"), &nam);
QLabel label;
label.setAlignment(Qt::AlignCenter);
label.setText("Downloading. . .");
label.setMinimumSize(640, 480);
label.show();
QObject::connect(&fileDownloader, &FileDownloader::downloaded, [&]{
QPixmap pixmap;
pixmap.loadFromData(fileDownloader.downloadedData());
label.setPixmap(pixmap);
});
return a.exec();
}
#include "main.moc"
If you are using this method to download large files, consider having a look at this question.
One solution could be to uses a QThreadPool. You simply feed it tasks (QRunnable) and it will handle the number of threads and the task queue for you.
However in your case this is not perfect because you will be limiting the number of simultaneous downloads to the number of threads created by QThreadPool (generally the number of CPU core you have).
To overcome this you will have to handle the QThread yourself and not use QThreadPool. You would create a small number of thread (see QThread::idealThreadCount()) and run multiple FileDownloader on each QThread.

How to properly stop QThread run() function?

I have one thread, producer and class consumer. Consumer is registered in QML, and producer is connected with signals to consumer. Producer send data to consumer, so consumer can update the model and GUI.
Code looks something like this:
Main function:
int main(int argc, char ** argv)
{
QGuiApplication app(argc, argv);
Producer producer;
Consumer consumer;
/* Connect signals between producer and consumer */
...
...
QQuickView view;
/* Set root context */
QQmlContext *ctxt = view.rootContext();
producer.start();
ctxt->setContextProperty("consumer", &consumer);
/*Connect signals between consumer and QML*/
...
view.show();
return app.exec();
}
Producer:
class Producer : public QThread
{
Q_OBJECT
protected:
void run()
{
while(true) {
if (someFlag == true)
{
// do some work
// emit signal with data to consumer
}
}
}
signals:
// Signals for sending data
};
The question is how to properly stop thread Producer when exit in application is pressed?
Instead of while(true) check for a "should end" condition, e.g. isInterruptionRequested().
Then, in main(), before returning, you tell the thread to stop, e.g. with QThread::requestInterruption and then wait on the thread
view.show();
const int ret = app.exec();
producer.requestInterruption();
producer.wait();
return ret;
I have a suggestion:
My way of doing is, Register the windows close event and do any sort of cleaning activities in it.
pseudo code below. try to fit it in your application.
Close filter event:
class CloseEventFilter : public QObject
{
Q_OBJECT
public:
CloseEventFilter(QObject *parent) : QObject(parent) {}
protected:
bool eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::Close)
{
// Destroy your thread here.
producer->exit();//Make your thread available
//... may be global....producer is derived from QThread.
}
return QObject::eventFilter(obj, event)
}
}
};
Registering to windows
//Registering window
QGuiApplication *app = new QGuiApplication(argc,argv);
QWindow* qtwindow = new QWindow();
CloseEventFilter *closeFilter = new CloseEventFilter(window);
qtwindow >installEventFilter(closeFilter);
Why thread exit funtion (from documentation)
void QThread::exit(int returnCode = 0)
Tells the thread's event loop to exit with a return code.
After calling this function, the thread leaves the event loop and returns from the call to QEventLoop::exec(). The QEventLoop::exec() function returns returnCode.
By convention, a returnCode of 0 means success, any non-zero value indicates an error.
Note that unlike the C library function of the same name, this function does return to the caller -- it is event processing that stops.
No QEventLoops will be started anymore in this thread until QThread::exec() has been called again. If the eventloop in QThread::exec() is not running then the next call to QThread::exec() will also return immediately.
See also quit() and QEventLoop.
The while (true) if (someFlag) loop will peg the CPU at 100% when there's no work to be done. You don't want that!
Alas, there's no need to make the Producer be a QThread. It can be a plain old QObject. There are different ways of doing it.
You can use a zero-duration timer to keep the event loop active in the thread:
class Producer : public QObject
{
Q_OBJECT
QBasicTimer m_timer;
void timerEvent(QTimerEvent * ev) override {
if (ev->timerId() == m_timer.timerId())
doWorkChunk();
}
void doWorkChunk() {
...
emit hasData(...);
}
public:
Producer(QObject * parent = nullptr) : QObject{parent} {
connect(this, &Producer::start, this, [this]{ m_timer.start(this, 0); });
connect(this, &Producer::stop, this, [this]{ m_timer.stop(); });
}
Q_SIGNAL void start();
Q_SIGNAL void stop();
Q_SIGNAL void hasData(const DataType &);
};
Instead of setting/clearing the flag, call start() and stop(). Then you can move the producer to any thread and it will work just fine, doing the right thing on exit (wrapping things up cleanly):
class Thread : public QThread {
using QThread::run;
public:
~Thread() { quit(); wait(); }
};
int main(int argc, char ** argv) {
QCoreApplication app{argc, argv};
Producer producer;
Thread thread; // must come after producer above, the order has meaning!
producer.moveToThread(&thread);
thread.start();
return app.exec();
}
You can use the Qt Concurrent framework, too:
class Producer : public QObject {
{
Q_OBJECT
volatile bool m_active = false;
void doWorkChunk() {
...
emit hasData();
}
void doWork() {
while (m_active) doWorkChunk();
}
public:
using QObject::QObject;
~Producer() { stop(); }
void start() {
m_active = true;
QtConcurrent::run(this, &Producer::doWork);
}
void stop() {
m_active = false;
}
Q_SIGNAL void hasData(const DataType &);
};

Qt Add loading widget screen while opening a project

I created an application in which I would like to add a loading screen when the application is opening a project because the loading of the project can be long and sometimes, the gui blocks so the user can think there is a crash.
So I tried with QThread, reading doc and "solved" examples on this forum but nothing to do, I can't make it work.
I have a MainWindow class which deals with GUI and this class is the one I create in the main function :
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
Then I have :
mainwindow.h
class MyThread;
class MainWindow : public QMainWindow
{
Q_OBJECT
...
private :
Controller *controller;//inherits from QObject and loads the project
QList<MyThread*> threads;
public slots :
void animateLoadingScreen(int inValue);
}
mainwindow.cpp
MainWindow::MainWindow(...)
{
controller=new Controller(...);
threads.append(new MyThread(30, this));
connect(threads[0], SIGNAL(valueChanged(int)), this, SLOT(animateLoadingScreen(int)));
}
void MainWindow::animateLoadingScreen(int inValue)
{
cout<<"MainWindow::animateLoadingScreen"<<endl;
widgetLoadingScreen->updateValue(inValue);//to update the animation
}
void MainWindow::openProject()
{
widgetLoadingScreen->show()://a widget containing a spinner for example
threads[0]->start();//I want to launch the other thread here
controller->openProject();
threads[0]->exit(0);//end of thread so end of loading screen
}
MyThread.h
class MyThread : public QThread
{
Q_OBJECT;
public:
explicit MyThread(int interval, QObject* parent = 0);
~MyThread();
signals:
void valueChanged(int);
private slots:
void count(void);
protected:
void run(void);
private:
int i;
int inc;
int intvl;
QTimer* timer;
};
MyThread.cpp
MyThread::MyThread(int interval, QObject* parent): QThread(parent), i(0), inc(-1), intvl(interval), timer(0)
{
}
void MyThread::run(void)
{
if(timer == 0)
{
timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(count()));
}
timer->start(intvl);
exec();
}
void MyThread::count(void)
{
if(i >= 100 || i <= 0)
inc = -inc;
i += inc;
emit valueChanged(i);
}
When I execute the app, and click on open button which launches MainWindow::openProject(), I get :
QObject: Cannot create children for a parent that is in a different thread.
(Parent is MyThread(0x5463930), parent's thread is QThread(0x3cd1f80), current thread is MyThread(0x5463930)
MainWindow::animateLoadingScreen
MainWindow::animateLoadingScreen
....
MainWindow::animateLoadingScreen
MainWindow::animateLoadingScreen
(and here the controller outputs. and no MainWindow::animateLoadingScreen anymore so the widget loading screen is never animated during the opening of the project)
So what do I have to do, what do I have to put in MyThread class, how to link its signal to MainWindow to update the loading screen. And I think that there may be a problem with widgetLoadingScreen which is created in MainWindow so if MainWindow is blocked beacause of the opening, the widgetLoadingScreen can't be updated since it is in the thread of MainWindow which handles the GUI ?!
I read :
http://www.qtcentre.org/wiki/index.php?title=Updating_GUI_from_QThread
but with that one, I got error message at runtime, it's the one I use in the code I give above
QObject: Cannot create children for a parent that is in a different thread.
(Parent is MyThread(0x41938e0), parent's thread is QThread(0x1221f80), current thread is MyThread(0x41938e0)
I tried that too :
How to emit cross-thread signal in Qt?
but, even if I don't have the error message at runtime, it's the same for the animation which is not updated.
I am completely lost, and I don't think it's something difficult to do to have a loading screen in a thread while the main thread is opening a project?!
Qt has a QSplashScreen class which you could use for a loading screen.
Besides this, one of the problems you have is due to thread affinity (the thread in which an object is running) and the fact that you've decided to inherit from QThread.
In my opinion, QThread has a misleading name. It's more of a thread controller and unless you want to change how Qt handles threads, you really shouldn't inherit from it and many will tell you that "You're doing it Wrong!"
By inheriting QThread, your instance of MyThread is running on the main thread. However, when a call to exec() is made, it starts the event loop for the new thread and all its children, which includes the QTimer, and tries to move it to the new thread.
The correct way to solve this is not to inherit from QThread, but to create a worker object, derived from QObject and move it to the new thread. You can read about how to 'Really Truly Use QThread' here.
1st problem - runtime error with QTimer
Problem is in void MyThread::run(void), line timer = new QTimer(this);. The thread instance is created in (and so owned by) different (main) thread then it represents. For this reason, you cannot use thread as parent for object created inside the thread. In your case, solution is simple - create the timer on stack.
Example:
void MyThread::run(void)
{
QTimer timer;
connect(&timer, SIGNAL(timeout()), this, SLOT(count()));
timer.start(intvl);
// Following method start event loop which makes sure that timer
// will exists. When this method ends, timer will be automatically
// destroyed and thread ends.
exec();
}
2nd problem - GUI not updating
Qt requires the GUI to be created and displayed from main application thread. This means that blocking main thread with big operation blocks whole GUI. There are only 2 possible solutions:
Move the heavy work to other (loading) thread. This is best solution and I believe it's worth the effort (I'm aware you wrote that this would be problematic).
During the heavy operation, call regularly QCoreApplication::processEvents() - each time you call this, events will be processed, which includes calling slots, key and mouse events, GUI redraw etc. The more often you call this, the more responsive GUI will be. This is simpler to implement (just put one line all over the code), but it's quite ugly and if the operation blocks on loading a file, the GUI will freeze for some time. That means that responsiveness of your GUI depends on interval between calls. (But if you call it too often, you will slow down the loading progress.) This technique is discouraged and it's best to avoid it...
The way I will do it based on the default example on Qthread Page:
Break the big controller->openProject(); such that it updates a variable
provide access to that variable using a signal ( like statusChanged(int)).
Do what you need to do once the signal is triggered.
The code looks like
class Worker : public QObject
{
Q_OBJECT
QThread workerThread;
private:
int status; //
bool hasworktoDo();
void doSmallWork();
public slots:
void doWork() {
while(hasworktoDo())
{
doSmallWork(); // fragments of controller->openProject();
++status; //previously set to 0
emit statusChanged(status);
}
emit resultReady(result);
}
signals:
void resultReady(const QString &result);
void statusChanged(int value);
};
class Controller : public QObject
{
Q_OBJECT
QThread workerThread;
public:
Controller() {
Worker *worker = new Worker;
worker->moveToThread(&workerThread);
connect(workerThread, SIGNAL(statusChanged(int)), this, SLOT(updateStatus(int)));
connect(workerThread, SIGNAL(finished()), worker, SLOT(deleteLater()));
connect(this, SIGNAL(operate()), worker, SLOT(doWork()));
connect(worker, SIGNAL(resultReady(QString)), this, SLOT(handleResults(QString)));
workerThread.start();
}
~Controller() {
workerThread.quit();
workerThread.wait();
}
public slots:
void handleResults(const QString &);
void updateStatus(int value)
{
int status = value;
//now you have what to use to update the ui
}
signals:
void operate(); //
};
If you have trouble breaking the openProject operations in small parts, it just mean it is pointless to say on the UI that you are at 50%... Just say Loading... and make the code simpler. Note that you don't need a timer in my case, as the worker sends regular updates

Play QSoundEffect in QThread

I can not get the QSoundEffect to play in a separate thread. Could you please tell me why is the sound played only by the first code snippet and not by the second?
//main.cpp
#include <QCoreApplication>
#include "SoundThread.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
// #1
QSoundEffect alarmSound;
alarmSound.setSource(QUrl::fromLocalFile(":/sound"));
alarmSound.play();
/* #2
SoundThread thread;
thread.start();
thread.wait();
*/
return a.exec();
}
and
//SoundThread.h
#ifndef SOUNDTHREAD_H
#define SOUNDTHREAD_H
#include <QThread>
#include <QtMultimedia/QSoundEffect>
class SoundThread : public QThread
{
Q_OBJECT
private:
void run()
{
QSoundEffect alarmSound;
alarmSound.setSource(QUrl::fromLocalFile(":/sound"));
alarmSound.play();
while(true){}
}
};
#endif // SOUNDTHREAD_H
From the Qt documentation on QThread: -
By default, run() starts the event loop by calling exec()
Since you've inherited from QThread, you now have a run function which doesn't call exec(). Therefore, the event loop is not running and is most likely required for playing the sound effect.
Calling exec() should be substituted for the while(true){} as exec() will wait until exit() is called.
Doing it properly, with moving an object to the thread, based on "How to Really Truly Use QThreads..."
class Worker : public QObject
{
Q_OBJECT
public:
Worker();
~Worker();
public slots:
void PlaySoundEffect();
signals:
void finished();
void error(QString err);
private:
// store the sound effect, so we can reuse it multiple times
QSoundEffect* m_pAlarmSound;
private slots:
};
Worker::Worker()
{
m_pAlarmSound = new QSoundEffect;
m_pAlarmSound.setSource(QUrl::fromLocalFile(":/sound"));
}
Worker::~Worker()
{
delete m_pAlarmSound;
m_pAlarmSound = nullptr; // C++ 11
}
void Worker::PlaySoundEffect()
{
m_pAlarmSound->play();
}
// setup the worker and move it to another thread...
MainWindow::MainWindow
{
QThread* thread = new QThread;
Worker* worker = new Worker();
worker->moveToThread(thread);
connect(worker, SIGNAL(error(QString)), this, SLOT(errorString(QString)));
connect(thread, SIGNAL(started()), worker, SLOT(PlaySoundEffect()));
connect(worker, SIGNAL(finished()), thread, SLOT(quit()));
connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater()));
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
thread->start();
// We can also connect a signal of an object in the main thread to the PlaySoundEffect slot
// Assuming MainWindow has declared a signal void Alert();
connect(this, &MainWindow::Alert, worker, &Worker::PlaySoundEffect);
// Then play the sound when we want: -
emit Alert();
}
While this seems like a lot of effort, there are many advantages of doing it this way. If, for example, you have a lot of sound effects, the method of inheriting QThread means that you're creating a thread per sound effect, which isn't ideal.
We could easily extend the above Worker object to hold a list of sound effects and play the one we want, by passing an enum into the PlaySoundEffect slot. As this thread is constantly running, playing sounds will incur less delay; it takes time and resources to create a thread at run-time.
You enter to an infinite loop at the end of your run function which causes to block the thread and consequently the QSoundEffect not working. It should be like:
void run()
{
QSoundEffect alarmSound;
alarmSound.setSource(QUrl::fromLocalFile(":/sound"));
alarmSound.play();
exec();
}
Replace while(true){} by QThread::run();, which will launch an internal event loop and will wait (sleep) for events, such as timer events and asynchronous signals calling slots, which is probably what is happening internally in QSoundEffect: When you call QSoundEffect::play() Some event (probably signals/slots) are queued in the event queue from within QThread, but nothing is processing the event queue. Remember: you are overriding virtual void run() and the original implementation was calling QThread::exec() for you. It is always a good idea to always call your super classes virtual function whenever you override them, as long as they are not pure virtual.
void run()
{
QSoundEffect alarmSound;
alarmSound.setSource(QUrl::fromLocalFile(":/sound"));
alarmSound.play();
QThread::run();
}
Some people suggested that calling QThread::exec() would do the trick. They may be right. I have to check the implementation of QThread to confirm that it is the only thing called in QThread::run() implementation. I personally think (by experience) that it is always safer to call your superclasse's virtual function in case there are other things called (other than QThread::exec() for this particular case).
Another option would be to move your QSoundEffect instance onto the thread and use signals and slots default auto-connection behaviour type to switch threads.
SoundPlayer.h:
#ifndef SOUNDPLAYER_H_
#define SOUNDPLAYER_H_
#include <QObject>
#include <QThread>
#include <QSoundEffect>
class SoundPlayer : public QObject
{
Q_OBJECT
public:
SoundPlayer();
signals:
void play();
private:
QThread m_thread;
QSoundEffect m_alarmSound;
};
#endif
SoundPlayer.cpp :
#include "SoundPlayer.h"
SoundPlayer()
{
m_alarmSound.setSource(QUrl::fromLocalFile(":/sound"));
m_alarmSound.moveToThread(&m_thread);
connect(this, SIGNAL(play()), &m_alarmSound, SLOT(play()));
m_thread.start(); // QThread::exec() will be called for you, making the thread wait for events
}
And then calling the play() signal would start playing in the correct thread.
SoundPlayer player;
emit player.play(); // m_alarmSound.play() will be called by m_thread

Categories

Resources