How to replace Qthread with QtConcurrent - c++

I have a SQL fetching class that connects to SQL and fetched the required data.
I wanted to this in another thread so I did that with QThread.
It is working as it should be.
But now I want to replace it with QTConcurrent. The problem I face with QTconcureent is that I need a connect command that initializes the database before the thread uses to do SQL query.
Here are my codes created is the public slot and qint64TotalSize is the public method of the SqlFetcher class.
Controller::Controller(QObject *parent) : QObject(parent)
{
SqlFetcher* m_Fetcher = new SqlFetcher();
qInfo() <<"Start"<< QThread::currentThread();
QFutureWatcher<void> watcher;
QFuture <void> future1 = QtConcurrent::run(m_Fetcher,&SqlFetcher::qint64TotalSize);
watcher.setFuture(future1);
//QThread* t1 = new QThread();
//m_Fetcher->moveToThread(t1);
//connect(t1, &QThread::started, m_Fetcher, &SqlFetcher::createDb);
//connect(t1, &QThread::started, m_Fetcher, &SqlFetcher::qint64TotalSize);
//t1->start();
qInfo() <<"Finish"<< QThread::currentThread();
}
void SqlFetcher::qint64TotalSize()
{
qint64 l_result= 0;
QSqlQuery l_query;
if (m_sqldb.isValid())
{
m_sqldb.open();
if ((m_sqldb.isOpen()))
{
l_query.prepare("SELECT COUNT(*) FROM table1");
l_query.exec();
if (l_query.next()) {
l_result= l_query.value(0).toInt();
}
m_sqldb.close();
}
}
qInfo() << l_result << QThread::currentThread();
}
void SqlFetcher::createDb()
{
m_sqldb = QSqlDatabase::addDatabase("QSQLITE");
m_sqldb.setDatabaseName("xyz.db");
qInfo() << "createDB" << QThread::currentThread();
}
My current output is
Start QThread(0x7feab4c0f060)
Finish QThread(0x7feab4c0f060)
0 QThread(0x7feab4d42070, name = "Thread (pooled)")
Expected Output or Output with Qthread is
Start QThread(0x7fe82140f060)
Finish QThread(0x7fe82140f060)
createDB QThread(0x7fe82155c840)
151 QThread(0x7fe82155c840)

Try executing the whole task in a run, e.g.
QtConcurrent::run([](){
SqlFetcher m_Fetcher;
m_Fetcher.createDb();
m_Fetcher.qint64TotalSize();
});
Since we're dealing with concurrency, it's safer to use named connections (otherwise the same default connection will be used every time, possibly by more than one thread). You can manage this by adding an argument to SqlFetcher::createDb:
void SqlFetcher::createDb(const QString & connectionName)
{
m_sqldb = QSqlDatabase::addDatabase("QSQLITE", connectionName);
// etc.
and to the lambda and the run function, as well:
QtConcurrent::run([](const QString & cn){
SqlFetcher m_Fetcher;
m_Fetcher.createDb(cn);
m_Fetcher.qint64TotalSize();
}, QString("TheConnectionName"));
In the other function, assign the database to the query, in construction:
void SqlFetcher::qint64TotalSize()
{
qint64 l_result= 0;
QSqlQuery l_query(m_sqldb);
//etc.

Related

How to control the return from a function using signal slots in qt

Hi I am requesting a json object from an API. I am using signal slots for reading the reply and then converting reply to json object. Till here everything is ok
but problem starts when I want to give output I am using a class member to give the ouput but class member is getting update at later time and function return the class member initial value.
My question is how to make sure that value is update before it returns.
I have tried QTimer but does not solve problem.
qint64 RestApiFetcher::totalSize()
{
request->setUrl(m_ipaddr + "?$count=true" + m_strtop);
IgnoreSSLCertification();
mNetReply = mNetManager->get(*request);
connect(mNetReply,&QIODevice::readyRead,this,&RestApiFetcher::dataReadSize);
connect(mNetReply,&QNetworkReply::finished,this,&RestApiFetcher::dataReadSizeFinished);
qDebug() << m_totalSize;
return m_totalSize;
}
void RestApiFetcher::dataReadSizeFinished()
{
if( mNetReply->error()){
qDebug() << "There was some error : " << mNetReply->errorString();
}else{
QJsonDocument doc = QJsonDocument::fromJson(*mDataBuffer);
QJsonObject data = doc.object();
m_totalSize = data["count"].toInt();
}
qDebug() << "#############";
qDebug() << m_totalSize;
}
and the output is
0
#############
150
What I need is
#############
150
150
basically the slot should run first and then the function should give output.
Update
qint64 RestApiFetcher::totalSize()
{
request->setUrl(m_ipaddr + "?$count=true" + m_strtop);
IgnoreSSLCertification();
mNetReply = mNetManager->get(*request);
QEventLoop loop;
connect(mNetReply,&QNetworkReply::finished,&loop,&QEventLoop::quit);
loop.exec(); // waits until `quit` called from signal above
disconnect(mNetReply,&QNetworkReply::finished,&loop,&QEventLoop::quit);
dataReadSizeFinished();
qDebug() << m_totalSize;
return m_totalSize;
}
and the output is
#############
0
0
A possible solution would be to use a QEventLoop to wait for the finished to occur. This is what I do if I need to wait on network communication before continuing. This would eliminate the need for dataReadSizeFinished, which could then be combined into your totalSize function, or just using a simple call to the function instead of through a signal.
qint64 RestApiFetcher::totalSize()
{
request->setUrl(m_ipaddr + "?$count=true" + m_strtop);
IgnoreSSLCertification();
QEventLoop loop;
connect(mNetReply,&QNetworkReply::finished,&loop,&QEventLoop::quit);
mNetReply = mNetManager->get(*request);
loop.exec(); // waits until `quit` called from signal above
disconnect(mNetReply,&QNetworkReply::finished,&loop,&QEventLoop::quit);
dataReadSize();
dataReadSizeFinished();
qDebug() << m_totalSize;
return m_totalSize;
}

How to pass a parameter using QSignalMapper, incompatible sender/receiver arguments

Implementation:
void Test::addProcessToList(const QString &command, const QString &id, const BasicInfo &basicInfo) {
QProcess *console = new QProcess();
QSignalMapper* signalMapper = new QSignalMapper (this) ;
connect (console, SIGNAL(readyRead()), signalMapper, SLOT(map())) ;
connect (console, SIGNAL(finished(int)), signalMapper, SLOT(processFinished(int))) ;
signalMapper->setMapping (console, id) ;
connect (signalMapper, SIGNAL(mapped(int)), this, SLOT(pidOut(QString))) ;
console->start(command);
}
void Test::registerProcess(QString id) {
QProcess *console = qobject_cast<QProcess*>(QObject::sender());
QByteArray processOutput = console->readAll();
int mainPID = parsePID(processOutput);
BasicInfo basicInfo;
qDebug() << "Registering id: " + id + " mainPID: " + mainPID;
if(mainPID != 0) {
Main::getInstance()->addProcessToList(mainPID, packageId, basicInfo);
} else {
qWarning() << "pidOut Error fetching mainPID";
}
}
void Test::processFinished(int exitCode) {
QProcess *console = qobject_cast<QProcess*>(QObject::sender());
QByteArray processOutput = console->readAll() + QString("Finished with code %1").arg(exitCode).toLatin1();
qDebug() << " processFinished: " + processOutput;
}
prototypes:
private
void addProcessToList(const QString &command, const QString &id, const BasicInfo &basicInfo);
private slots:
void registerProcess(QString);
void processFinished(int);
I get this errors when I call connect, which tells me I'm doing it wrong:
"QObject::connect: Incompatible sender/receiver arguments
QSignalMapper::mapped(int) --> Test::registerProcess(QString)"
I'm not understanding where I'm suppose to specify my parameter (QString id) so that registerProcess will receive it when it's called? I'm assuming I'm doing this part wrong, cut from above:
signalMapper->setMapping (console, id) ;
connect (signalMapper, SIGNAL(mapped(int)), this, SLOT(pidOut(QString))) ;
QSignalMapper can emit either mapped(const QString & text) or mapped(int i) signals. The type is defined by setMapping(QObject * sender, int id) or setMapping(QObject * sender, const QString & text).
That led to confusion probably by autocompletion in
connect (signalMapper, SIGNAL(mapped(int)), this, SLOT(pidOut(QString)));
The types of signal and slot must be the same for connection.
You set string mapping (QString &id), so the signal in the connection should be QString:
connect (signalMapper, SIGNAL(mapped(QString)), this, SLOT(pidOut(QString)));
Update
After deeper review of the code flow I suspect that you wanted to connect mapper to registerProcess() slot instead of pidOut(). In that slot you can have as an argument QString id that was passed to signalMapper in setMapping() call. That is the purpose of using QSignalMapper.
However, beside that id it is not possible to extract console pointer, since in that case sender() is signalMapper object. If it is the case, QSignalMapper cannot help you here. You should use direct connection of console and this on readReady (of course with slot of this with void argument as readReady()). To get the string id in that slot it is possible to use simple QMap<QProces*, QString> map stored as a Test class member.
// addProcessToList(...):
map[console] = id;
//registerProcess():
QString id = map[console];
//processFinished(...):
map.remove(console);
By the way, it is not needed to created a new instance of QSignalMapper for each map item.

Qt - resolve two sequential calls on a slot and perform action only once

I have a CodeEditor class which inherits from QPlainTextEdit. This class has to emit requests when textCursor()'s position or selection changes. The thing is, I don't want 2 calls to assembleIntellisenseRequest and pickListSortRequest signals when both cursorPositionChanged and selectionChanged is emitted. I solved this with adding bool update_ member to CodeEditor, which is set to true in constructor. 500ms delay is just for clarity.
void CodeEditor::makeConnections()
{
auto updateRequest = [this]()
{
if(update_ && !textCursor().hasSelection())
{
update_ = false;
QTimer::singleShot(500, [this]() { update_ = true; });
emit assembleIntellisenseRequest(textCursor().blockNumber());
emit pickListSortRequest(textCursor().positionInBlock());
}
};
connect(this, &CodeEditor::cursorPositionChanged, updateRequest);
connect(this, &CodeEditor::selectionChanged, updateRequest);
}
Is there any better way to accomplish this? Also, why was in this case, when lambda captures by reference this1: printout not equal to this:? It was silent, I just knew, that update_ is still false.
auto updateRequest = [this]()
{
cout << "this: " << this << endl;
if(update_ && !textCursor().hasSelection())
{
update_ = false;
QTimer::singleShot(500, [&]()
{
cout << "this1: " << this << endl;
update_ = true;
});
emit assembleIntellisenseRequest(textCursor().blockNumber());
emit pickListSortRequest(textCursor().positionInBlock());
}
};
Thank you in advance.
You can apply next pattern to your code:
class MyClass : public QObject
{
private slots:
void updateRequest();
private:
QTimer *_timer;
CodeEditor *_editor;
};
MyClass::MyClass()
{
// Init members
// ...
_timer->setSingleShot( true );
_timer->setInterval( 0 );
connect( _editor, &CodeEditor:: cursorPositionChanged, _timer, &QTimer::start);
connect( _editor, &CodeEditor:: selectionChanged, _timer, &QTimer::start);
connect( _timer, &QTimer::timeout, this, &MyClass::updateRequest );
}
In this solution, timer is a "proxy for signals". Each time signal is emited timer will start immedeately (when flow will return to an event loop). Each emitting of signal will call QTimer::start slot. But all calls of start will place only one call of timeout signal to event queue. So, when control flow will return to event loop, your updateRequest slot will be called only once, even if a lot of signals were emited.
QTimer is a "Qt way" to replace your update_ variable, without any timeouts.

QSqlDatbase is closed before the query finishes

I am trying to implement a database object for my application, but it became a nightmare when I started using multiple connections. Below, you can see my database C++ class code:
// here are the declarations
QString server_addr;
QString username;
QString password;
QString database_name;
QSqlDatabase connection;
QString error;
QString connectionName;
QSqlQuery m_query;
// and here are the definitions
database::database(QString connectionName) {
preferences p; p.read();
QString iConnectionName = (connectionName == "") ? default_connection_name : connectionName;
this->connectionName = iConnectionName;
if (QSqlDatabase::contains(iConnectionName))
this->connection = QSqlDatabase::database(iConnectionName);
else this->connection = QSqlDatabase::addDatabase("QMYSQL", iConnectionName);
this->connection.setHostName(p.database->server_addr);
this->connection.setUserName(p.database->username);
this->connection.setPassword(p.database->password);
this->connection.setDatabaseName(p.database->database_name);
this->connection.setPort(p.database->serverPort);
if (!connection.open())
{
this->error = this->connection.lastError().text();
}
else this->error = "";
}
QSqlQuery database::query(QString query_text) {
this->m_query = QSqlQuery(query_text, this->connection);
this->m_query.exec();
return m_query;
}
database::~database() {
if (!this->m_query.isActive()) QSqlDatabase::removeDatabase(this->connectionName);
qDebug() << "database object destroyed\n";
}
The problem occurs in a another class (that uses the database):
databaseAdaptor::databaseAdaptor() {
this->db = database();
// other construction operations
}
void databaseAdaptor::fetch() {
QSqlQuery q = db.query("SELECT * FROM `activities`");
qDebug() << "Rows count: " << q.numRowsAffected();
}
It worked some versions in the past, but for some reason, now the output from qDebug() is
Rows count: -1 // and it should be 2 for my current version of database.
In the Qt Documentation, it is said that this class' members are thread-safe. Could this be the problem? I mean, could the thread end before the queries finish their execution?
If so, how can I keep the connection open until all the queries finished their execution?
(I know it's much to read, but I am sure that other might have this problem at some point, so please take your time and read it). Thank you!

how to return an Qt object from the result of an thread (Qtfutur)

i'm trying to load some data from a server and fill a Qt list. i want to run the dowloanding in a thread. so there is the code of:
principal function in the App.cpp
loadInterestPlaces(QString& urlInterestPlaces) {
LoadData* Data = new LoadData(urlInterestPlaces);
QFuture< list <InterestPlace *> > future = QtConcurrent::run(Data,
&LoadData::startLoading);
// Invoke our onLoadingFinished slot after the loading has finished.
bool ok = connect(&m_watcher, SIGNAL(finished()), this,
SLOT(onLoadingFinished()));
Q_ASSERT(ok);
Q_UNUSED(ok);
// starts watching the given future
m_watcher.setFuture(future);
}
void ApplicationUI::onLoadingFinished() {
qDebug() << "Loading finished";
interestPlacesList = m_watcher.future().result();
qDebug() << "List size= " << interestPlacesList.size();
}
}
the LoadData.cpp file : this is the code of the startloanding function :
std::list<InterestPlace *> LoadData::startLoading()
{
QNetworkAccessManager* netManager = new QNetworkAccessManager(this);
const QUrl url(_URL);
QNetworkRequest request(url);
QNetworkReply* reply = netManager->get(request);
netManager->moveToThread(this->thread());
netManager->setParent(this);
bool ok = connect(reply, SIGNAL(finished()), this, SLOT(onReplyFinished()));
qDebug() << reply->isFinished();
Q_ASSERT(ok);
Q_UNUSED(ok);
qDebug() << "load data: liste size" <<interestPlacesList.size();
return interestPlacesList;
}
Finally inside the SLOT onreplyfinished i parse the data and fill the list.
But the problem here, the QFuture is finished before the downloading so that the list is always empty.
How could i return the list filled just after the execution of the onReplyFinished ?
You may be making this more complex than you need to. Here is what I do to get data off the web. In my case I'm downloading a large ZIP file, writing it out and then unziping it, but the basic steps are the same (I have omitted a lot of my specific code for clarity):
void HtmlDownloader::startDownload() {
// Creates the network request and sets the destination URL
QNetworkRequest request = QNetworkRequest();
request.setUrl(mUrl);
// Creates the network access manager and connects a custom slot to its
// finished signal. Checks the return value for errors.
QNetworkAccessManager *networkAccessManager =
new QNetworkAccessManager(this);
bool c = connect(networkAccessManager, SIGNAL(finished(QNetworkReply*)),
this, SLOT(requestFinished(QNetworkReply*)));
Q_ASSERT(c);
// Indicate that the variable res isn't used in the rest of the app, to prevent
// a compiler warning
Q_UNUSED(c);
// Sends the request
QNetworkReply *reply = networkAccessManager->get(request);
c = connect(reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(onDownloadProgress(qint64,qint64)));
Q_ASSERT(c);
}
void HtmlDownloader::requestFinished(QNetworkReply *reply) {
// Handle the reply data...
// Check the network reply for errors
if (reply->error() == QNetworkReply::NoError) {
// use reply->readAll() or reply->read() to get the returned data
// and process into a list. The list can't be returned but could
// be sent as a payload of a signal, or stored in a data member and
// a signal sent to indicate it is available.
}
reply->deleteLater();
}
void HtmlDownloader::onDownloadProgress(qint64 bytesRecieved, qint64 bytesTotal) {
emit downloadProgress(bytesRecieved, bytesTotal);
}