Qt: Is the QBuffer thread safe in this code? - c++

The code is given as:
class USBCamCaptureThread : public QThread
{
Q_OBJECT
void run() {
while (!threadQuit){
if (imageReaded){
buffer->reset();
Request = http->get (url->path(),buffer););
}
MG::SLEEP::msleep(250);
}
}
public:
USBCamCaptureThread(){
QFile file("setting_files/cameraIP.txt");
QString line = "192.168.1.5:80"; //default value
if(file.open(QIODevice::ReadOnly)) {
QTextStream in(&file);
line = in.readLine();
}
file.close();
url = new QUrl(line);
http = new QHttp(this);
buffer = new QBuffer(&bytes);
threadQuit = false;
imageReaded = true;
}
~USBCamCaptureThread(){
}
public slots:
void Finished(int requestId, bool error){
QImage localImage;
localImage.loadFromData(bytes);
QImg = localImage;
bytes.clear();
emit signalUSBImageRead();
}
Note that the Finished() is in the main thread, and the run() is in a worker thread.
May question is by reading/writing to the bytes and buffer in separate threads, will it cause thread related issues?

If Finished is called while run is active, then yes, you have a problem. Sharing data between two threads that run in a pipeline (serially) isn't necessarily a problem. Accessing non-threadsafe, shared data between two threads at the same time is always a problem. I haven't used Qt in many years so QBuffer or QImage::loadFromData may offer thread safety guarantees of their own as well.

Related

QThread stuck in second run

I have a QThread on my embedded device. every time I run the application my thread stuck after second run. I try to kill my thread after first run. Still device stuck after second run. I couldn't run my thread correctly.
Here is my code;
void ThreadCurrency::run()
{
QMutex mutex;
mutex.lock();
if(this->CurrencyStop == true)
{
mutex.unlock();
return;
}
QByteArray strdata;
// Create QProcess object
processCurrency = new QProcess();
processCurrency->start("curl --insecure -v --cacert /data/ca/cert.pem https://secure.*******************/fx.jsp");
if (processCurrency->waitForStarted(-1))
{
while(processCurrency->waitForReadyRead(-1))
{
strdata += processCurrency->readAllStandardOutput();
}
QMessageBox msgBox1;
msgBox1.setWindowTitle("eCode Read");
msgBox1.setText(strdata);
msgBox1.exec();
}
else
{
while(processCurrency->waitForReadyRead(-1))
{
strdata += processCurrency->readAllStandardError();
}
QMessageBox msgBox1;
msgBox1.setWindowTitle("eCode Error");
msgBox1.setText(strdata);
msgBox1.exec();
}
mutex.unlock();
sleep(1);
//*****************************************************************
emit CurrencyChanged(aGBP, aEUR, aUSD, sGBP, sEUR, sUSD);
}
** The Output shows in a json format:**
{
"date": "20171107", "currency": {
"dolar": {
"buy": "3,8200",
"sale": "3,9050",
"e_sale": "3,8200"
},
}
}
Thank you for suggestion. The QMutexLocker can’t work in my embedded device. QMutex and the QThread is the closes I can get in my device.
My problem is the below line: emit CurrencyChanged(aGBP, aEUR, aUSD, sGBP, sEUR, sUSD); The line runs but It doesn’t fire onCurrencyChanged SLOT. Anything in onCurrencyChanged doesn’t run. My main thread code is:
currencyThread = new ThreadCurrency (this);
connect(currencyThread,SIGNAL(CurrencyChanged(QString, QString, QString, QString, QString, QString)), this, SLOT(onCurrencyChanged (QString, QString, QString, QString, QString, QString)));
currencyThread->CurrencyStop = false;
currencyTimer = new QTimer(this);
connect(currencyTimer, SIGNAL(timeout()),this, SLOT(showCurrencyStatus()));
currencyTimer->start(30000);
void MainWindow::onCurrencyChanged(QString aGBP, QString aEUR, QString aUSD, QString sGBP, QString sEUR, QString sUSD)
{
// SHOW Currency
ui->lblALIS_STG->setText(aGBP);
ui->lblALIS_EUR->setText(aEUR);
ui->lblALIS_USD->setText(aUSD);
QCoreApplication::processEvents();
}
1) You are not allowed to access Widgets in a thread besides the main thread.
Thus move all QMessageBox code to the main thread (e.g. after receiving the CurrencyChanged signal).
2) Make sure, that the CurrencyChanged signal is connected using a QueuedConnection, otherwise, your GUI will be executed inside the secondary worker thread.
3) Do not use QMutex directly, use a QMutexLocker instead
4) Why do you need the Mutex anyhow? Locking based on (arbitrary) input thus arbitrary time is close to deadlocking your application.

How to make blocking tcp socket with Qt?

I work with QTcpSocket. I need any write/read calls to the socket to be synchronous (blocking).
I know there is waitForReadyRead() and waitForBytesWritten(), but those two methods are marked in Qt documentation as they can fail randomly under Windows. I cannot affort this.
The blocking read is the most important (as reading comes always after writting a command to the other peer, so I know that if data reaches the other peer, it will answer).
I have tried 2 approaches.
First:
QByteArray readBytes(qint64 count)
{
int sleepIterations = 0;
QByteArray resultBytes;
while (resultBytes.size() < count && sleepIterations < 100)
{
if (socket->bytesAvailable() == 0)
{
sleepIterations++;
QThread::msleep(100);
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
continue;
}
resultBytes += socket->read(qMin(count, socket->bytesAvailable()));
}
return resultBytes;
}
This should wait for bytes to be available for reading on the socket, processing the event loop in the mean time, so the socket is doing it's necessary internal stuff.
Unfortunately - for unknown to me reason - the bytesAvailable() sometimes returns correct number of bytes, but sometimes it never returns anything greater than 0.
I know in fact that there was data to be read, because it used to work with the second approach (but it has it's own problems).
Second:
I have a kind of signal "blocker", which blocks current context and processes event loop, until certain signal is emitted. This is the "blocker":
SignalWait.h:
class SignalWait : public QObject
{
Q_OBJECT
public:
SignalWait(QObject *object, const char *signal);
bool wait(int msTimeout);
private:
bool called = false;
private slots:
void handleSignal();
};
SignalWait.cpp:
SignalWait::SignalWait(QObject* object, const char* signal) :
QObject()
{
connect(object, signal, this, SLOT(handleSignal()));
}
bool SignalWait::wait(int msTimeout)
{
QTime timer(0, 0, 0, msTimeout);
timer.start();
while (!called && timer.elapsed() < msTimeout)
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
return called;
}
void SignalWait::handleSignal()
{
called = true;
}
and then I used it like this:
SignalWait signalWait(socket, SIGNAL(readyRead()));
// ...
// socket->write(...);
// ...
if (!signalWait.wait(30000))
{
// error
return;
}
bytes = socket->read(size);
This approach seems to be working better, but it also fails from time to time. I don't know why. It's like the readyRead() signal was never emitted and the SignalWait keeps waiting, until it times out.
I'm out of ideas. What is the proper way to deal with it?
I would suggest to use the asynchronous approach but if you really want to go with the synchronous way, then a better way is to use a local event loop:
QTimer timer;
timer.setSingleShot(true);
QEventLoop loop;
loop.connect(socket, SIGNAL(readyRead()), SLOT(quit()));
connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
while (resultBytes.size() < count)
{
timer.start(msTimeout);
loop.exec();
if(timer.isActive())
resultBytes += socket->read(qMin(count, socket->bytesAvailable()));
else
break;
}
Here it waits until count bytes are read or the the timeout reaches.

Qt: Is QHttp's get function thread safe?

I have the following code:
class USBCamCaptureThread : public QThread
{
Q_OBJECT
void run() {
while (!threadQuit){
mutex.lock();
bytes.clear();
buffer->reset();
Request = http->get (url->path(),buffer););
mutex.unlock();
MG::SLEEP::msleep(250);
}
}
public:
USBCamCaptureThread(){
QFile file("setting_files/cameraIP.txt");
QString line = "192.168.1.5:80"; //default value
if(file.open(QIODevice::ReadOnly)) {
QTextStream in(&file);
line = in.readLine();
}
file.close();
url = new QUrl(line);
http = new QHttp(this);
buffer = new QBuffer(&bytes);
threadQuit = false;
imageReaded = true;
open(0);
}
~USBCamCaptureThread(){
}
public slots:
void Finished(int requestId, bool error){
QImage localImage;
mutex.lock();
localImage.loadFromData(bytes);
mutex.unlock();
QImg = localImage;
emit signalUSBImageRead();
}
bool open(int camID){
buffer->open(QIODevice::WriteOnly);
http->setHost(url->host());
connect(http, SIGNAL(requestFinished(int, bool)),this, SLOT(Finished(int, bool)));
return true;
}
Note that the run() is in a worker thread and the Finished() is in the main thread.
My question is: since the documentation says the QHttp get function is "The function does not block; instead, it returns immediately. The request is scheduled, and its execution is performed asynchronously."
Is it possible that when the requestFinished is emitted and Finished() is triggered, it happens that the run() function has the mutex and therefore the QBytearray bytes.clear() is called and when Finished() got the mutex, there is nothing to be loaded from the QBytearray bytes?
Furthermore, if the process of http->get() is scheduled to be processed later, does that mean its operation on buffer is no longer protected by the mutex?

Splitting a file into several parts

I'm trying to make a File copying application in Qt. I need to split the source file into several files so that my copy function can copy portions of data one by one and update the QProgressBar. To update the progress accurately I need to split the source file in 1% of its original size. Is my approach wrong. I'm unable to find much resources on this topic.How can I split the source file into several parts of equal size?
The following is a self-contained sketch of such an asynchronous file copier. There are some shortcomings:
The error reporting needs to report QFile error codes as well.
A custom mutex locker class is really needed to deal with mutex RAII.
When looking at the code, ensure that you consider the implementation and the interface (its use) separately. The interface makes it easy to use. The implementation's relative complexity makes the easy to use interface possible.
#include <QApplication>
#include <QByteArray>
#include <QProgressDialog>
#include <QFileDialog>
#include <QBasicTimer>
#include <QElapsedTimer>
#include <QThread>
#include <QMutex>
#include <QFile>
#include <limits>
class FileCopier : public QObject {
Q_OBJECT
QMutex mutable m_mutex;
QByteArray m_buf;
QBasicTimer m_copy, m_progress;
QString m_error;
QFile m_fi, m_fo;
qint64 m_total, m_done;
int m_shift;
void close() {
m_copy.stop();
m_progress.stop();
m_fi.close();
m_fo.close();
m_mutex.unlock();
}
/// Takes the error string from given file and emits an error indication.
/// Closes the files and stops the copy. Always returns false
bool error(QFile & f) {
m_error = f.errorString();
m_error.append(QStringLiteral("(in %1 file").arg(f.objectName()));
emit finished(false, m_error);
close();
return false;
}
void finished() {
emitProgress();
emit finished(m_done == m_total, m_error);
close();
}
void emitProgress() {
emit progressed(m_done, m_total);
emit hasProgressValue(m_done >> m_shift);
}
void timerEvent(QTimerEvent * ev) {
if (ev->timerId() == m_copy.timerId()) {
// Do the copy
qint64 read = m_fi.read(m_buf.data(), m_buf.size());
if (read == -1) { error(m_fi); return; }
if (read == 0) return finished();
qint64 written = m_fo.write(m_buf.constData(), read);
if (written == -1) { error(m_fo); return; }
Q_ASSERT(written == read);
m_done += read;
}
else if (ev->timerId() == m_progress.timerId())
emitProgress();
}
Q_INVOKABLE void cancelImpl() {
if (!m_fi.isOpen()) return;
m_error = "Canceled";
finished();
}
public:
explicit FileCopier(QObject * parent = 0) :
QObject(parent),
// Copy 64kbytes at a time. On a modern hard drive, we'll copy
// on the order of 1000 such blocks per second.
m_buf(65536, Qt::Uninitialized)
{
m_fi.setObjectName("source");
m_fo.setObjectName("destination");
}
/// Copies a file to another with progress indication.
/// Returns false if the files cannot be opened.
/// This method is thread safe.
Q_SLOT bool copy(const QString & src, const QString & dst) {
bool locked = m_mutex.tryLock();
Q_ASSERT_X(locked, "copy",
"Another copy is already in progress");
m_error.clear();
// Open the files
m_fi.setFileName(src);
m_fo.setFileName(dst);
if (! m_fi.open(QIODevice::ReadOnly)) return error(m_fi);
if (! m_fo.open(QIODevice::WriteOnly)) return error(m_fo);
m_total = m_fi.size();
if (m_total < 0) return error(m_fi);
// File size might not fit into an integer, calculate the number of
// binary digits to shift it right by. Recall that QProgressBar etc.
// all use int, not qint64!
m_shift = 0;
while ((m_total>>m_shift) >= std::numeric_limits<int>::max()) m_shift++;
emit hasProgressMaximum(m_total>>m_shift);
m_done = 0;
m_copy.start(0, this);
m_progress.start(100, this); // Progress is emitted at 10Hz rate
return true;
}
/// This method is thread safe only when a copy is not in progress.
QString lastError() const {
bool locked = m_mutex.tryLock();
Q_ASSERT_X(locked, "lastError",
"A copy is in progress. This method can only be used when"
"a copy is done");
QString error = m_error;
m_mutex.unlock();
return error;
}
/// Cancels a pending copy operation. No-op if no copy is underway.
/// This method is thread safe.
Q_SLOT void cancel() {
QMetaObject::invokeMethod(this, "cancelImpl");
}
/// Signal for progress indication with number of bytes
Q_SIGNAL void progressed(qint64 done, qint64 total);
/// Signals for progress that uses abstract integer values
Q_SIGNAL void hasProgressMaximum(int total);
Q_SIGNAL void hasProgressValue(int done);
///
Q_SIGNAL void finished(bool ok, const QString & error);
};
/// A thread that is always destructible: if quits the event loop and waits
/// for it to finish.
class Thread : public QThread {
public:
~Thread() { quit(); wait(); }
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QString src = QFileDialog::getOpenFileName(0, "Source File");
if (src.isEmpty()) return 1;
QString dst = QFileDialog::getSaveFileName(0, "Destination File");
if (dst.isEmpty()) return 1;
QProgressDialog dlg("File Copy Progress", "Cancel", 0, 100);
Q_ASSERT(!dlg.isModal());
Thread thread;
FileCopier copier;
copier.moveToThread(&thread);
thread.start();
dlg.connect(&copier, SIGNAL(hasProgressMaximum(int)),
SLOT(setMaximum(int)));
dlg.connect(&copier, SIGNAL(hasProgressValue(int)),
SLOT(setValue(int)));
copier.connect(&dlg, SIGNAL(canceled()), SLOT(cancel()));
a.connect(&copier, SIGNAL(finished(bool,QString)), SLOT(quit()));
// The copy method is thread safe.
copier.copy(src, dst);
return a.exec();
}
#include "main.moc"
Usually file copying with progress bar is done in this way:
open the source file (for read)
open the destination file (for write)
read a block (64 kB or so) from source file and write it to destination file
update the progress bar
repeat steps 3. and 4. until end of source file
done :) close the files
No need to split file into multiple files. Just process the file by small blocks (block after block), not whole file at once.
The "split file into several files before copying" approach is wrong - this split approach is equally expensive as copying, whole operation would took twice as long and you would need to update progress bar during this splitting too.

QNetworkAccessManager multiple uploads fail

In my App, I have a method to upload files to the server, this works fine.
But when I call this method multiple times at once (like iterating over the result of a chooseFilesDialog) the first 7 (more or less) files are uploaded correctly, the others never get uploaded.
I think this has to be linked with the fact the server doesn't allow more than X connections from the same source maybe?
How can I make sure the upload waits for a free, established connection?
this is my method:
QString Api::FTPUpload(QString origin, QString destination)
{
qint64 timestamp = QDateTime::currentMSecsSinceEpoch();
QUrl url("ftp://ftp."+getLSPro("domain")+destination);
url.setUserName(getLSPro("user"));
url.setPassword(getLSPro("pwd"));
QFile *data = new QFile(origin, this);
if (data->open(QIODevice::ReadOnly))
{
QNetworkAccessManager *nam = new QNetworkAccessManager();
QNetworkReply *reply = nam->put(QNetworkRequest(url), data);
reply->setObjectName(QString::number(timestamp));
connect(reply, SIGNAL(uploadProgress(qint64, qint64)), SLOT(uploadProgress(qint64, qint64)));
return QString::number(timestamp);
}
else
{
qDebug() << "Could not open file to FTP";
return 0;
}
}
void Api::uploadProgress(qint64 done, qint64 total) {
QNetworkReply *reply = (QNetworkReply*)sender();
emit broadCast("uploadProgress","{\"ref\":\""+reply->objectName()+"\" , \"done\":\""+QString::number(done)+"\", \"total\":\""+QString::number(total)+"\"}");
}
First, don't create a QNetworkManager every time you start an upload.
Second, you definitely have to delete everything you new(), otherwise you are left with memory leaks. This includes the QFile, the QNetworkManager AND the QNetworkReply (!).
Third, you have to wait for the finished() signal.
Api::Api() { //in the constructor create the network access manager
nam = new QNetworkAccessManager()
QObject::connect(nam, SIGNAL(finished(QNetworkReply*)), this, SLOT(finished(QNetworkReply*)));
}
Api::~Api() { //in the destructor delete the allocated object
delete nam;
}
bool Api::ftpUpload(QString origin, QString destination) {
qint64 timestamp = QDateTime::currentMSecsSinceEpoch();
QUrl url("ftp://ftp."+getLSPro("domain")+destination);
url.setUserName(getLSPro("user"));
url.setPassword(getLSPro("pwd"));
//no allocation of the file object;
//will automatically be destroyed when going out of scope
//I use readAll() (see further) to fetch the data
//this is OK, as long as the files are not too big
//If they are, you should allocate the QFile object
//and destroy it when the request is finished
//So, you would need to implement some bookkeeping,
//which I left out here for simplicity
QFile file(origin);
if (file.open(QIODevice::ReadOnly)) {
QByteArray data = file.readAll(); //Okay, if your files are not too big
nam->put(QNetworkRequest(url), data);
return true;
//the finished() signal will be emitted when this request is finished
//now you can go on, and start another request
}
else {
return false;
}
}
void Api::finished(QNetworkReply *reply) {
reply->deleteLater(); //important!!!!
}