Close previous slot if still running - qt

I got a searchfield that triggers a database search.
connect(searchField,SIGNAL(textChanged(QString)),this,SLOT(searchSong(QString)));
But the database search takes longer then typing the next search condition.
SLOTS are queued but I don't want this. When a new signal is send the previous SLOT needs to be cancelled and restarted.
How can I do this?

You are going the wrong way. What you have to do is create a flag that indicates if there is a pending request, and if there is when searchSong is called then cancel it and launch a new request:
*.h
private:
bool is_busy;
*.cpp
// constructor
FooClass::FooClass(foo_paramenters):
BaseClass(args), is_busy(false)
{
// some code
connect(searchField, &QLineEdit::textChanged, this, &FooClass::searchSong);
}
void FooClass::searchSong(const QString & text){
if(is_busy)
cancel_request();
is_busy = true;
send_request(text);
}
void FooClass::receive_answer_from_request(){
// some code
is_busy = false;
}

Related

Signals emitted from a QThread worker class do not arrive

I have this simplified code:
class MyCustomObject {
};
class DeviceConnection : public QObject {
Q_OBJECT
public:
explicit DeviceConnection(QObject* const parent = nullptr);
signals:
void readFinished(MyCustomObject result);
public slots:
void readFromDevice();
};
DeviceConnection::readFromDevice() {
/* ... */
emit readFinished(MyCustomObject());
}
void MainWindow::on_actionRead_triggered() {
QThread* const thread = new QThread(this);
DeviceConnection* const connection = new DeviceConnection();
connection->moveToThread(thread);
thread->start();
connect(connection, &DeviceConnection::readFinished, this, [=](MyCustomObject data) {
/* This never runs. */
connection->deleteLater();
thread->quit();
});
QTimer::singleShot(0, connection, &DeviceConnection::readFromDevice);
}
This starts reading just fine. I can see in the debugger that I am getting to the emit line, and I am getting there in the thread. But I can also see in the debugger, and in the behavior of the code, that the readFinished lambda is never called. This is also true with slots that aren't lambdas. What's the problem?
Edit: This code runs fine when I don't use an extra thread, but of course it blocks the main thread while readFromDevice() runs.
I figured it out. Unfortunately I simplified the important bit away when I first asked the question, but I just edited it back in.
The problem is that MyCustomObject cannot be enqueued in the Qt message queue. To do that, you need to run this:
qRegisterMetaType<MyCustomObject>("MyCustomObject");
or
// ideally just after the definition for MyCustomObject
Q_DECLARE_METATYPE(MyCustomObject);
// any time before you want to enqueue one of these objects
qRegisterMetaType<MyCustomObject>();
your defined signal should take an argument of QString type.

Task doesn't update local variable

I'm using a Task to read a text file, the task is invoked when the user clicks "open file" menu, it is supposed to read the text file, and then update the local variable "text", the problem occurs at the first try, if i open a file, nothing happens, and the value of the text string stays as it is, if i open any file again, everything works as expected, i couldn't find the cause of this.
The method that has the task
private void readFile(File file){
Task<String> task = new Task<String>() {
#Override
protected String call() {
List<String> list = EditorUtils.readFromFile(file);
String str = list.stream().collect(Collectors.joining("\n"));
return str;
}
};
task.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
#Override
public void handle(WorkerStateEvent t) {
setCurrentText(task.getValue());
}
});
task.setOnFailed(e -> setCurrentText("FAILED"));
Thread t = new Thread(task);
t.setDaemon(true);
t.start();
}
SetCurrentText
private void setCurrentText(String text){
this.text = text;
}
The method of the controller
#FXML
void openMenuItemClick(ActionEvent event) {
fileChooser.setTitle("title");
fileChooser.getExtensionFilters().add
(new FileChooser.ExtensionFilter("TXT files (*.txt)", "*.txt"));
File file = fileChooser.showOpenDialog(open.getParentPopup().getScene().getWindow());
if (file != null){
readFile(file);
System.out.println(text); //prints null since "text" isn't initialized yet
}
}
EditorUtils#readFromFile
public static List<String> readFromFile(File file){
List<String> lines = new ArrayList<>();
try {
lines = Files.readAllLines(Paths.get(file.getPath()), StandardCharsets.UTF_8);
} catch (IOException e) {
e.printStackTrace();
}
return lines;
}
Your readFile method creates a Task, gives it to a Thread, launches the Thread, and returns. You then try to immediately print out the value of text. There's no guarantee your Task will have completed by the time you call println(text). In fact, it's very likely your Task has not completed yet. But that's not the only problem.
The call to readFile and println are both done on the same thread—in this case, the JavaFX Application Thread. The problem here is that the EventHandler you pass to setOnSucceeded will be invoked on the FX thread as well. The way this is achieved internally is with a Platform.runLater call which schedules the action with the FX thread to be ran some time in the future. This can't happen while the FX thread is executing openMenuItemClick and must wait until the method returns.
What this all means is that setCurrentText will never run until after the call to println. But by the second time openMenuItemClick is invoked the text will have been set1. So what you're seeing the second time is actually the result of the first Task.
If you want to do something with text once the Task completes then you should do it inside the onSucceeded or onFailed handler. Or you can make text a StringProperty and observe it for changes.
1. Technically, it may have been set. There's still no guarantee the Task has completed by then.
This is perfectly normal behaviour when using multiple threads. You access the file from a task running on background thread. On completion this task triggers an update on the JavaFX application thread.
By the time readFile returns the task may not have been completed. The fact that Task uses Platform.runLater to execute the onSucceeded handler results in this handler never being invoked before the openMenuItemClick method completes, even if the file is read before System.out.println is reached.
If you need to update the GUI based on the result of the Task, you should do so from the event handler. The code updating the text field runs after the System.out.println(text); statement. The second time you start the task, you print results of the task started the first time the menu item was clicked, not the new one. You can verify this by moving the println to the beginning of the openMenuItemClick method.

QTimer timeout and QMutex interaction

Let's say we have some basic timer and a slot which is invoked periodically.
SomeObject::SomeObject()
{
QTimer *timer = new QTimer;
connect(timer , SIGNAL(timeout()), this, SLOT(updateState()));
}
void SomeObject::updateState()
{
// some operations leading to update of updatedValue
}
I also have function from same object which forces updates and returns some value.
bool SomeObject::getLatestState()
{
updateState();
return updatedValue;
}
This function may be directly called from different threads. This brings question of thread safety to mind. Simple mutex lock in getLatestState will not help as in some rare cases getLatestState is called from another thread that starts updateState. And at the same time timer's timeout may occur. Can you help me handle this situation properly?
QMutexLocker can be used in such situations
this my exampl
static QMutex mainMutex;
.....
MainController* MainController::s_instance = NULL;
.....
MainController* MainController::getInstance()
{
if(!s_instance){
QMutexLocker lock(&mainMutex);
if(!s_instance){
s_instance = new MainController;
}
}
return s_instance;
}
getInstance() function directly called from different threads.

SysOperation Framework suppress infolog messages for ReliableAsynchronous but keep them in batch history

I'm just getting my feet wet with the SysOperation framework and I have some ReliableAsynchronous processes that run and call info("starting...") etc.
I want these infolog messages so that when I look in the BatchHistory, I can see them for purposes of investigating later.
But they also launch to the client, from the batch. And I can tell they're from the batch because you can't double click on the infologs to go to the source. Is there someway to either suppress these from popping up on the user's screen and only show in the batch log?
EDIT with some code:
User clicks a button on form action pane that calls an action menu item referencing a class.
In the class, the new method:
public void new()
{
super();
this.parmClassName(classStr(MyControllerClass));
this.parmMethodName(methodStr(MyControllerClass, pickTransLines));
this.parmExecutionMode(SysOperationExecutionMode::ReliableAsynchronous);
// This is meant to be running as a batch task, so don't load syslastvalue
this.parmLoadFromSysLastValue(false);
}
The main method hit from the menu item:
public static void main (Args _args)
{
MyControllerClass controller = new MyControllerClass();
MyContract contract;
WMSOrderTrans wmsOrderTrans;
RefRecId refRecId;
if (_args && _args.dataset() == tableNum(WMSOrderTrans) && _args.record())
{
contract = controller.getDataContractObject();
contract.parmRefRecId(_args.record().RecId);
controller.parmShowDialog(false);
refRecId = controller.doBatch().BatchJobId;
// This creates a batch tracking record
controller.updateCreateTracking(refRecId, _args.record().RecId);
}
}
The controller method that gets launched:
// Main picking method
private void pickTransLines(MyContract_contract)
{
MyTrackingTable tracking;
boolean finished;
BatchHeader batchHeader = BatchHeader::getCurrentBatchHeader();
boolean updateTracking = false;
// NOTE - This infolog launches after a few seconds to the user, but
// you can't double click on the info message to go to the code
// because it's fired from the batch somehow.
info(strFmt("Working on wmsordertrans.recid == %1", _contract.parmRefRecId()));
// Create/Update batch tracker if needed
if (this.isInBatch())
{
// NOTE - This code gets executed so we ARE in batch
this.updateTrackingStuff(...);
}
// Do the pick work
finished = this.doPick(_contract);
if(!finished)
throw error("An error occurred during the picking process.");
}
Then a split second later this launches to my session:
Look at the SysOperationServiceController.afterOperation method,:
[...]
if (_executionMode == SysOperationExecutionMode::ReliableAsynchronous)
{
batch = this.operationReturnValue();
if (batch)
{
infolog.view(Batch::showLog(batch.RecId));
}
}
[...]
This is the code that shows the infolog to the screen for reliable asynchronous processed.
You can create your own controller by extending SysOperationServiceController and use that on your menu item or in code, so do that and overwrite the afterOperation on your new controller, for example like this (didn't test but should work in your case):
if (_executionMode != SysOperationExecutionMode::ReliableAsynchronous)
{
super(_executionMode, _asyncResult);
}

Blocking a Qt application during downloading a short file

I'm writing an application using Qt4.
I need to download a very short text file from a given http address.
The file is short and is needed for my app to be able to continue, so I would like to make sure the download is blocking (or will timeout after a few seconds if the file in not found/not available).
I wanted to use QHttp::get(), but this is a non-blocking method.
I thought I could use a thread : my app would start it, and wait for it to finish. The thread would handle the download and quit when the file is downloaded or after a timeout.
But I cannot make it work :
class JSHttpGetterThread : public QThread
{
Q_OBJECT
public:
JSHttpGetterThread(QObject* pParent = NULL);
~JSHttpGetterThread();
virtual void run()
{
m_pHttp = new QHttp(this);
connect(m_pHttp, SIGNAL(requestFinished(int, bool)), this, SLOT(onRequestFinished(int, bool)));
m_pHttp->setHost("127.0.0.1");
m_pHttp->get("Foo.txt", &m_GetBuffer);
exec();
}
const QString& getDownloadedFileContent() const
{
return m_DownloadedFileContent;
}
private:
QHttp* m_pHttp;
QBuffer m_GetBuffer;
QString m_DownloadedFileContent;
private slots:
void onRequestFinished(int Id, bool Error)
{
m_DownloadedFileContent = "";
m_DownloadedFileContent.append(m_GetBuffer.buffer());
}
};
In the method creating the thread to initiate the download, here is what I'm doing :
JSHttpGetterThread* pGetter = new JSHttpGetterThread(this);
pGetter->start();
pGetter->wait();
But that doesn't work and my app keeps waiting. It looks lit the slot 'onRequestFinished' is never called.
Any idea ?
Is there a better way to do what I'm trying to do ?
Instead of using a thread you can just go into a loop which calls processEvents:
while (notFinished) {
qApp->processEvents(QEventLoop::WaitForMore | QEventLoop::ExcludeUserInput);
}
Where notFinished is a flag which can be set from the onRequestFinished slot.
The ExcludeUserInput will ensure that GUI related events are ignored while waiting.
A little late but:
Do not use these wait loops, the correct way is to use the done() signal from QHttp.
The requestFinished signal from what I have seen is just for when your application has finished the request, the data may still be on its way down.
You do not need a new thread, just setup the qhttp:
httpGetFile= new QHttp();
connect(httpGetFile, SIGNAL(done(bool)), this, SLOT(processHttpGetFile(bool)));
Also do not forget to flush the file in processHttpGetFile as it might not all be on the disk.
you have to call QThread::quit() or exit() if you are done - otherwise your thread will run forever...
I chose to implement David's solution, which seemed to be the easiest.
However, I had handle a few more things :
I had to adapt the QEventLoop enum values for Qt4.3.3 (the version I'm using);
I had to track the request Id, to make sure to exit the while loop when the download request is finished, and not when another request is finished;
I added a timeout, to make sure to exit the while loop if there is any problem.
Here is the result as (more or less) pseudo-code :
class BlockingDownloader : public QObject
{
Q_OBJECT
public:
BlockingDownloaderBlockingDownloader()
{
m_pHttp = new QHttp(this);
connect(m_pHttp, SIGNAL(requestFinished(int, bool)), this, SLOT(onRequestFinished(int, bool)));
}
~BlockingDownloader()
{
delete m_pHttp;
}
QString getFileContent()
{
m_pHttp->setHost("www.xxx.com");
m_DownloadId = m_pHttp->get("/myfile.txt", &m_GetBuffer);
QTimer::singleShot(m_TimeOutTime, this, SLOT(onTimeOut()));
while (!m_FileIsDownloaded)
{
qApp->processEvents(QEventLoop::WaitForMoreEvents | QEventLoop::ExcludeUserInputEvents);
}
return m_DownloadedFileContent;
}
private slots:
void BlockingDownloader::onRequestFinished(int Id, bool Error)
{
if (Id == m_DownloadId)
{
m_DownloadedFileContent = "";
m_DownloadedFileContent.append(m_GetBuffer.buffer());
m_FileIsDownloaded = true;
}
}
void BlockingDownloader::onTimeOut()
{
m_FileIsDownloaded = true;
}
private:
QHttp* m_pHttp;
bool m_FileIsDownloaded;
QBuffer m_GetBuffer;
QString m_DownloadedFileContent;
int m_DownloadId;
};
I used QNetworkAccsessManager for same necessity. Because this class managing connections RFC base (6 proccess same time) and non-blocking.
http://qt-project.org/doc/qt-4.8/qnetworkaccessmanager.html
How about giving the GUI some amount of time to wait on the thread and then give up.
Something like:
JSHttpGetterThread* pGetter = new JSHttpGetterThread(this);
pGetter->start();
pGetter->wait(10000); //give the thread 10 seconds to download
Or...
Why does the GUI thread have to wait for the "downloader thread" at all? When the app fires up create the downloader thread, connect the finished() signal to some other object, start the downloader thread, and return. When the thread has finished, it will signal the other object which can resume your process.

Resources