My C++ QTWidget application (both Qt 5.15.2 & also Qt 6.2.0(much worse)) are locking up during a large (~10,000 row) table (QTableView) update from a worker thread.
The rows are very simple and contain 7 string columns:
using GPSRecord = std::array<std::string, 7>;
using GPSData = std::vector<GPSRecord>;
I followed the documentation and some forum suggestions to try to make GUI responsive during the update - splitting the data from the worker into manageable chunks of 50 rows for example - emitting these in a loop until the entire table was sent to the main GUI.
The worker thread is as follows:
// fire off a separate asynchronous thread to parse the flight plan
// and create a simulated flight from the lambda parameters below.
mFuture = std::async(std::launch::async,
[this, xTrackMax, cruiseSpeed, cruiseAlt, gpsInterval, callSign, mode] {
// note since this is created on a separate thread, we have to signal
// batches of rows to the update the GUI thread.
try {
const auto flightPlan =
CoPilotUtils::importFlightPlan(*mFlightPlanPath);
const auto flightTrackData =
CoPilotUtils::simulateFlight(*flightPlan, xTrackMax,
cruiseSpeed, cruiseAlt, gpsInterval, mode);
GPSData gpsData;
auto index = 0;
const auto utcTime = floor<seconds>(system_clock::now());
for (const auto& next : *flightTrackData) {
// "Timestamp", "UTC", "Callsign", "Position",
// "Altitude", "Speed", "Direction"
// "Timestamp"
const auto timeStamp = std::format("{:010}",
static_cast<uint32_t>(next.mUTCSecs));
// "UTC"
const auto utc = std::format("{:%FT%T}Z", utcTime);
// "Callsign"
// "Position"
const auto position = std::format("{:.6f},{:.6f}",
next.mLatitude, next.mLongitude);
// "Altitude"
const auto altitude = std::format("{}", static_cast<
uint32_t>(next.mAltitudeFeet));
// "Speed"
const auto speed = std::format("{}", static_cast<
uint32_t>(next.mSpeedKnots));
// "Direction"
const auto direction = std::format("{}", static_cast<
int32_t>(next.mTrueTrack));
gpsData.emplace_back(GPSRecord{
timeStamp, utc,
callSign.toStdString(),
position, altitude,
speed, direction });
if (gpsData.size() >= 50) {
// queued data from worker to GUI thread
emit flightRowsAdded(gpsData);
gpsData.clear();
// yield to allow UI to free up
//std::this_thread::sleep_for(milliseconds(10));
}
++index;
}
if (!gpsData.empty()) {
emit flightRowsAdded(gpsData);
gpsData.clear();
}
// enable gui widgets upon successful import
emit flightPlanImported(
std::format("{} imported with {} legs",
mFlightPlanPath->filename().string(),
flightPlan->size()).c_str());
}
catch (const UtlIOException& rEx) {
// enable gui widgets upon failed import
emit flightPlanImportFailed(
std::format("error: {}", rEx.what()).c_str());
}
});
}
During the signal/slot setup, I connect the worker thread's signal to a slot in the main GUI thread where the QStandardItemModel is updated. This uses a Qt::QueuedConnection
connection, as these are separate threads and the data needs to be copied.
// Use Qt::QueuedConnection - copy the results to the GUI thread in a queue
connect(this, &MainWindow::flightRowsAdded,
this, &MainWindow::updateFlightTable, Qt::QueuedConnection);
passing batches of rows (50 at a time) for insertion into the model - thus updating the view.
I tried inserting 50ms delays between the worker thread batch emits. I also tried inserting QCoreApplication::processEvents() in the slot where the model is updated (after processing the batch of 50 rows - this caused a crash), but neither of these helped to make the GUI responsive.
The GUI slot that performs the update is as follows
void
MainWindow::updateFlightTable(const GPSData& rGPSData)
{
// "Timestamp", "UTC", "Callsign", "Position", "Altitude", "Speed", "Direction"
auto index = mTableModel->rowCount();
mTableModel->setRowCount(mTableModel->rowCount() +
static_cast<int>(rGPSData.size()));
for (const auto& nextRow : rGPSData) {
// "Timestamp"
mTableModel->setItem(index, 0, new QStandardItem(nextRow[0].c_str()));
// "UTC"
mTableModel->setItem(index, 1, new QStandardItem(nextRow[1].c_str()));
// "Callsign"
mTableModel->setItem(index, 2, new QStandardItem(nextRow[2].c_str()));
// "Position"
mTableModel->setItem(index, 3, new QStandardItem(nextRow[3].c_str()));
// "Altitude"
mTableModel->setItem(index, 4, new QStandardItem(nextRow[4].c_str()));
// "Speed"
mTableModel->setItem(index, 5, new QStandardItem(nextRow[5].c_str()));
// "Direction"
mTableModel->setItem(index, 6, new QStandardItem(nextRow[6].c_str()));
// next row
index++;
}
// try to handle background events accumulated between batch updates
// neither of the 2 tricks below work - both crash
//QCoreApplication::processEvents();
//QCoreApplication::sendPostedEvents();
}