MBoss131
16th March 2016, 18:12
We have a QSpinBox which is connected to a SLOTfor the valueChanged SIGNAL. When the user presses the up arrow, the valueChanged SLOT is called and processing begins. This is, of course, as desired.
However, while in the SLOT valueChanged we do our processing. When the processing takes too long, we get another valueChanged (but not from the user, from, we believe, inside QT). This causes us to process again (and the spin box value to increment). I will stop and mention at this point that we will be moving to a more multi-threaded approach, but we wanted to get our prototype done before we delved into that. I see now reason why this should not work as expected. an XCode build works as desired.
To further the issue, if QApplication::processEvents() is called after the long processing, then the valueChanged SLOT is called again; becoming re-entrant. I read an old posting: Preventing a Slot to be called again before it has finished running, which suggested that this was impossible whether asynchronous or synchronous, and/or was due to heap corruption. One poster pointed out some ideas, one with processEvents. With all that being said, I took to writing a simple application that exposed the issue I am having.
I won't call it a QT bug, but it sure shows the slot being called multiple times for a single user click of the up arrow. It also shows a re-entrancy issue when processEvents() is called and the user has clicked the arrow several times. Attached is the sample code. Now I will walk thru the sample code and the output.
My question is: Is this a bug or something we are not realizing about slots in a single threaded QT application. Code and output follows.
#include "mainwindow.h"
#include <QDebug>
#include <thread>
#include <chrono>
// set to connect/disconnect the slot
#define CONNECT_DISCONNECT_FIX 0
// set PROCESS_EVENTS to 0 to not issue QApplication::processEvents();
#define PROCESS_EVENTS 0
// set SLEEP_MILLISECONDS to how long to delay in slot (< 350 seems to work, 2000 is usually guaranteed
#define SLEEP_MILLISECONDS 2000
// show some output
#define DEBUG_OUTPUT(strDescription,bInSlot,nID) qDebug() << (strDescription) << "\n\tID: " << (nID) << "\n\tInSlot: "<< (bInSlot) << "\n\tThreadID: " << _threadid << "\n\tTimeStamp: " << std::chrono::high_resolution_clock::now().time_sin ce_epoch().count() << "\n\n";
MainWindow::MainWindow(QWidget* parent) :
QMainWindow(parent)
{
m_UI.setupUi(this);
bool bResult = connect(m_UI.spinBox,SIGNAL(valueChanged(int)),thi s,SLOT(OnSlotSpinBox(int)),Qt::QueuedConnection);
}
MainWindow::~MainWindow()
{
}
void MainWindow::OnSlotSpinBox(int nValue)
{
#if CONNECT_DISCONNECT_FIX
bool bResult = disconnect(m_UI.spinBox,SIGNAL(valueChanged(int)), this,SLOT(OnSlotSpinBox(int)));
#endif
// unique ID
static uint32_t snID = 0;
// transfer to local, so it is right when re-entrancy occurs
uint32_t nID = ++snID;
static bool sbInSlot = false;
DEBUG_OUTPUT("[enter] OnSlotSpinBox",sbInSlot,nID);
if (!sbInSlot) {
sbInSlot = true;
DEBUG_OUTPUT("[sleep-start] OnSlotSpinBox",sbInSlot,nID);
std::this_thread::sleep_for(std::chrono::milliseco nds(SLEEP_MILLISECONDS));
#if PROCESS_EVENTS
DEBUG_OUTPUT("[processEvents-start] OnSlotSpinBox",sbInSlot,nID);
QApplication::processEvents();
DEBUG_OUTPUT("[processEvents-end] OnSlotSpinBox",sbInSlot,nID);
#endif
DEBUG_OUTPUT("[sleep-end] OnSlotSpinBox",sbInSlot,nID);
sbInSlot = false;
}
else {
DEBUG_OUTPUT("[RE-ENTRANCY DETECTED] OnSlotSpinBox",sbInSlot,nID);
}
DEBUG_OUTPUT("[exit] OnSlotSpinBox",sbInSlot,nID);
#if CONNECT_DISCONNECT_FIX
bResult = connect(m_UI.spinBox,SIGNAL(valueChanged(int)),thi s,SLOT(OnSlotSpinBox(int)),Qt::QueuedConnection);
#endif
}
Scenario #1. Select up arrow once. (no processEvents() or connect/disconnect). The output clearly shows that the SLOT was called twice in a row.
[enter] OnSlotSpinBox
ID: 1
InSlot: false
ThreadID: 14080
TimeStamp: 14581510183319621
[sleep-start] OnSlotSpinBox
ID: 1
InSlot: true
ThreadID: 14080
TimeStamp: 14581510183319621
[sleep-end] OnSlotSpinBox
ID: 1
InSlot: true
ThreadID: 14080
TimeStamp: 14581510203443750
[exit] OnSlotSpinBox
ID: 1
InSlot: false
ThreadID: 14080
TimeStamp: 14581510203443750
[enter] OnSlotSpinBox
ID: 2
InSlot: false
ThreadID: 14080
TimeStamp: 14581510203443750
[sleep-start] OnSlotSpinBox
ID: 2
InSlot: true
ThreadID: 14080
TimeStamp: 14581510203443750
[sleep-end] OnSlotSpinBox
ID: 2
InSlot: true
ThreadID: 14080
TimeStamp: 14581510223567879
[exit] OnSlotSpinBox
ID: 2
InSlot: false
ThreadID: 14080
TimeStamp: 14581510223567879
Scenario #2. Select up arrow five times as fast as possible. (processEvents() and no connect/disconnect). The output clearly shows that the SLOT was called while in the slot (re-entrancy).
[enter] OnSlotSpinBox
ID: 1
InSlot: false
ThreadID: 16500
TimeStamp: 14581513537474084
[sleep-start] OnSlotSpinBox
ID: 1
InSlot: true
ThreadID: 16500
TimeStamp: 14581513537474084
[processEvents-start] OnSlotSpinBox
ID: 1
InSlot: true
ThreadID: 16500
TimeStamp: 14581513557598213
[processEvents-end] OnSlotSpinBox
ID: 1
InSlot: true
ThreadID: 16500
TimeStamp: 14581513557598213
[sleep-end] OnSlotSpinBox
ID: 1
InSlot: true
ThreadID: 16500
TimeStamp: 14581513557598213
[exit] OnSlotSpinBox
ID: 1
InSlot: false
ThreadID: 16500
TimeStamp: 14581513557598213
[enter] OnSlotSpinBox
ID: 2
InSlot: false
ThreadID: 16500
TimeStamp: 14581513557598213
[sleep-start] OnSlotSpinBox
ID: 2
InSlot: true
ThreadID: 16500
TimeStamp: 14581513557598213
[processEvents-start] OnSlotSpinBox
ID: 2
InSlot: true
ThreadID: 16500
TimeStamp: 14581513577722342
[enter] OnSlotSpinBox
ID: 3
InSlot: true
ThreadID: 16500
TimeStamp: 14581513577722342
[RE-ENTRANCY DETECTED] OnSlotSpinBox
ID: 3
InSlot: true
ThreadID: 16500
TimeStamp: 14581513577722342
[exit] OnSlotSpinBox
ID: 3
InSlot: true
ThreadID: 16500
TimeStamp: 14581513577722342
[enter] OnSlotSpinBox
ID: 4
InSlot: true
ThreadID: 16500
TimeStamp: 14581513577722342
[RE-ENTRANCY DETECTED] OnSlotSpinBox
ID: 4
InSlot: true
ThreadID: 16500
TimeStamp: 14581513577722342
[exit] OnSlotSpinBox
ID: 4
InSlot: true
ThreadID: 16500
TimeStamp: 14581513577722342
[enter] OnSlotSpinBox
ID: 5
InSlot: true
ThreadID: 16500
TimeStamp: 14581513577722342
[RE-ENTRANCY DETECTED] OnSlotSpinBox
ID: 5
InSlot: true
ThreadID: 16500
TimeStamp: 14581513577722342
[exit] OnSlotSpinBox
ID: 5
InSlot: true
ThreadID: 16500
TimeStamp: 14581513577722342
[processEvents-end] OnSlotSpinBox
ID: 2
InSlot: true
ThreadID: 16500
TimeStamp: 14581513577722342
[sleep-end] OnSlotSpinBox
ID: 2
InSlot: true
ThreadID: 16500
TimeStamp: 14581513577722342
[exit] OnSlotSpinBox
ID: 2
InSlot: false
ThreadID: 16500
TimeStamp: 14581513577722342
However, while in the SLOT valueChanged we do our processing. When the processing takes too long, we get another valueChanged (but not from the user, from, we believe, inside QT). This causes us to process again (and the spin box value to increment). I will stop and mention at this point that we will be moving to a more multi-threaded approach, but we wanted to get our prototype done before we delved into that. I see now reason why this should not work as expected. an XCode build works as desired.
To further the issue, if QApplication::processEvents() is called after the long processing, then the valueChanged SLOT is called again; becoming re-entrant. I read an old posting: Preventing a Slot to be called again before it has finished running, which suggested that this was impossible whether asynchronous or synchronous, and/or was due to heap corruption. One poster pointed out some ideas, one with processEvents. With all that being said, I took to writing a simple application that exposed the issue I am having.
I won't call it a QT bug, but it sure shows the slot being called multiple times for a single user click of the up arrow. It also shows a re-entrancy issue when processEvents() is called and the user has clicked the arrow several times. Attached is the sample code. Now I will walk thru the sample code and the output.
My question is: Is this a bug or something we are not realizing about slots in a single threaded QT application. Code and output follows.
#include "mainwindow.h"
#include <QDebug>
#include <thread>
#include <chrono>
// set to connect/disconnect the slot
#define CONNECT_DISCONNECT_FIX 0
// set PROCESS_EVENTS to 0 to not issue QApplication::processEvents();
#define PROCESS_EVENTS 0
// set SLEEP_MILLISECONDS to how long to delay in slot (< 350 seems to work, 2000 is usually guaranteed
#define SLEEP_MILLISECONDS 2000
// show some output
#define DEBUG_OUTPUT(strDescription,bInSlot,nID) qDebug() << (strDescription) << "\n\tID: " << (nID) << "\n\tInSlot: "<< (bInSlot) << "\n\tThreadID: " << _threadid << "\n\tTimeStamp: " << std::chrono::high_resolution_clock::now().time_sin ce_epoch().count() << "\n\n";
MainWindow::MainWindow(QWidget* parent) :
QMainWindow(parent)
{
m_UI.setupUi(this);
bool bResult = connect(m_UI.spinBox,SIGNAL(valueChanged(int)),thi s,SLOT(OnSlotSpinBox(int)),Qt::QueuedConnection);
}
MainWindow::~MainWindow()
{
}
void MainWindow::OnSlotSpinBox(int nValue)
{
#if CONNECT_DISCONNECT_FIX
bool bResult = disconnect(m_UI.spinBox,SIGNAL(valueChanged(int)), this,SLOT(OnSlotSpinBox(int)));
#endif
// unique ID
static uint32_t snID = 0;
// transfer to local, so it is right when re-entrancy occurs
uint32_t nID = ++snID;
static bool sbInSlot = false;
DEBUG_OUTPUT("[enter] OnSlotSpinBox",sbInSlot,nID);
if (!sbInSlot) {
sbInSlot = true;
DEBUG_OUTPUT("[sleep-start] OnSlotSpinBox",sbInSlot,nID);
std::this_thread::sleep_for(std::chrono::milliseco nds(SLEEP_MILLISECONDS));
#if PROCESS_EVENTS
DEBUG_OUTPUT("[processEvents-start] OnSlotSpinBox",sbInSlot,nID);
QApplication::processEvents();
DEBUG_OUTPUT("[processEvents-end] OnSlotSpinBox",sbInSlot,nID);
#endif
DEBUG_OUTPUT("[sleep-end] OnSlotSpinBox",sbInSlot,nID);
sbInSlot = false;
}
else {
DEBUG_OUTPUT("[RE-ENTRANCY DETECTED] OnSlotSpinBox",sbInSlot,nID);
}
DEBUG_OUTPUT("[exit] OnSlotSpinBox",sbInSlot,nID);
#if CONNECT_DISCONNECT_FIX
bResult = connect(m_UI.spinBox,SIGNAL(valueChanged(int)),thi s,SLOT(OnSlotSpinBox(int)),Qt::QueuedConnection);
#endif
}
Scenario #1. Select up arrow once. (no processEvents() or connect/disconnect). The output clearly shows that the SLOT was called twice in a row.
[enter] OnSlotSpinBox
ID: 1
InSlot: false
ThreadID: 14080
TimeStamp: 14581510183319621
[sleep-start] OnSlotSpinBox
ID: 1
InSlot: true
ThreadID: 14080
TimeStamp: 14581510183319621
[sleep-end] OnSlotSpinBox
ID: 1
InSlot: true
ThreadID: 14080
TimeStamp: 14581510203443750
[exit] OnSlotSpinBox
ID: 1
InSlot: false
ThreadID: 14080
TimeStamp: 14581510203443750
[enter] OnSlotSpinBox
ID: 2
InSlot: false
ThreadID: 14080
TimeStamp: 14581510203443750
[sleep-start] OnSlotSpinBox
ID: 2
InSlot: true
ThreadID: 14080
TimeStamp: 14581510203443750
[sleep-end] OnSlotSpinBox
ID: 2
InSlot: true
ThreadID: 14080
TimeStamp: 14581510223567879
[exit] OnSlotSpinBox
ID: 2
InSlot: false
ThreadID: 14080
TimeStamp: 14581510223567879
Scenario #2. Select up arrow five times as fast as possible. (processEvents() and no connect/disconnect). The output clearly shows that the SLOT was called while in the slot (re-entrancy).
[enter] OnSlotSpinBox
ID: 1
InSlot: false
ThreadID: 16500
TimeStamp: 14581513537474084
[sleep-start] OnSlotSpinBox
ID: 1
InSlot: true
ThreadID: 16500
TimeStamp: 14581513537474084
[processEvents-start] OnSlotSpinBox
ID: 1
InSlot: true
ThreadID: 16500
TimeStamp: 14581513557598213
[processEvents-end] OnSlotSpinBox
ID: 1
InSlot: true
ThreadID: 16500
TimeStamp: 14581513557598213
[sleep-end] OnSlotSpinBox
ID: 1
InSlot: true
ThreadID: 16500
TimeStamp: 14581513557598213
[exit] OnSlotSpinBox
ID: 1
InSlot: false
ThreadID: 16500
TimeStamp: 14581513557598213
[enter] OnSlotSpinBox
ID: 2
InSlot: false
ThreadID: 16500
TimeStamp: 14581513557598213
[sleep-start] OnSlotSpinBox
ID: 2
InSlot: true
ThreadID: 16500
TimeStamp: 14581513557598213
[processEvents-start] OnSlotSpinBox
ID: 2
InSlot: true
ThreadID: 16500
TimeStamp: 14581513577722342
[enter] OnSlotSpinBox
ID: 3
InSlot: true
ThreadID: 16500
TimeStamp: 14581513577722342
[RE-ENTRANCY DETECTED] OnSlotSpinBox
ID: 3
InSlot: true
ThreadID: 16500
TimeStamp: 14581513577722342
[exit] OnSlotSpinBox
ID: 3
InSlot: true
ThreadID: 16500
TimeStamp: 14581513577722342
[enter] OnSlotSpinBox
ID: 4
InSlot: true
ThreadID: 16500
TimeStamp: 14581513577722342
[RE-ENTRANCY DETECTED] OnSlotSpinBox
ID: 4
InSlot: true
ThreadID: 16500
TimeStamp: 14581513577722342
[exit] OnSlotSpinBox
ID: 4
InSlot: true
ThreadID: 16500
TimeStamp: 14581513577722342
[enter] OnSlotSpinBox
ID: 5
InSlot: true
ThreadID: 16500
TimeStamp: 14581513577722342
[RE-ENTRANCY DETECTED] OnSlotSpinBox
ID: 5
InSlot: true
ThreadID: 16500
TimeStamp: 14581513577722342
[exit] OnSlotSpinBox
ID: 5
InSlot: true
ThreadID: 16500
TimeStamp: 14581513577722342
[processEvents-end] OnSlotSpinBox
ID: 2
InSlot: true
ThreadID: 16500
TimeStamp: 14581513577722342
[sleep-end] OnSlotSpinBox
ID: 2
InSlot: true
ThreadID: 16500
TimeStamp: 14581513577722342
[exit] OnSlotSpinBox
ID: 2
InSlot: false
ThreadID: 16500
TimeStamp: 14581513577722342