0

I think I'm having a problem similar to the one in Qt crash when redrawing Widget, and switching to Qt::QueuedConnection fixed the problem. However, in my case, both the signal emitter and received are always in the same thread (the main thread).

I have a QAbstractItemModel with entry rows, and a QSortFilterProxyModel for filtering. Since the model can be very large, I wanted to make a progress bar when filtering. Updating the filter basically does this in a slot that is connected to a QAction::toggled signal:

m_ProgressBar = new QProgressBar(); // Put into status bar etc.
auto connection = connect(m_filteredModel, SIGNAL(filterProgressChanged(int)), m_ProgressBar, SLOT(setValue(int)), Qt::QueuedConnection);
m_filteredModel->UpdateFilter();
delete m_ProgressBar;
disconnect(connection);

UpdateFilter basically does some housekeeping and then calls invalidate, making the filter model requery filterAcceptsRow for every row. The filter model then emits the filterProgressChanged(int) signal within filterAcceptsRow (works by incrementing a counter and dividing by the source model's row count, and is only emitted when the actual int progress value changes). UpdateFilter returns when the filtering is complete. The progress bar is not deleted until then (verified), so it should work in my opinion. Not deleting the progress bar leads to getting a new one every call, but the crash is still the same.

Everything is done in the main thread: Creating the progress bar, calling UpdateFilter, emitting the filterProgressChanged signal. However, when the connection is created as Qt::AutoConnection, i.e. direct, it crashes (only when disabling the filter, for some reason) within repainting the progress bar. The same happens when I call setValue directly in my own event handler, which is what I did prior to switching to the current code.

Now I have a solution that works, but I don't understand why the original code does not work. I thought that a DirectConnection would only make an actual difference when sender and receiver of the signal are in different threads, but they're not. You can easily see in the stack trace that everything happens within the same thread, and that is even true with the queued connection.

So, what's going wrong in the original code? Is there something I just missed? Is there any way to get more information out of the actual crash?

I only found out that in void QRasterPaintEngine::clip(const QRect &rect, Qt::ClipOperation op), state() returns 0, and the code assumes it never returns 0, which is the immediate crash reason, but likely not the reason. And the stack trace points to painting as the problem area, that's all I saw when debugging this.

I'm on Windows with Qt 5.4.2 (also tried 5.7), and with MSVC 2013, if any of that matters.


Edit: As requested by code_fodder, I added the UpdateFilter and emit code (actualFilterFunction performs the actual filtering, but has nothing to do with signals or GUI or anything).

void MyModel::UpdateFilter() {
    m_filterCounter = 0;
    m_lastReportedProgress = -1;
    invalidate();
}

bool MyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const {
    m_filterCounter++;
    int progress = (100 * m_filterCounter) / m_sourceModel->rowCount();
    if (progress != m_lastReportedProgress) {
        emit filterProgressChanged(m_lastReportedProgress = progress);
    }
    return actualFilterFunction();
}
Community
  • 1
  • 1
OregonGhost
  • 23,359
  • 7
  • 71
  • 108
  • can you try replacing the `delete m_ProgressBar` with `m_ProgressBar->deleteLater()`? – λuser Jul 26 '16 at 08:32
  • I just tried, doesn't make any difference. But I wouldn't have expected, since the delete is only called after having returned from UpdateFilter, and the crash happends within UpdateFilter's stack trace. – OregonGhost Jul 26 '16 at 08:36
  • Regarding the delete - you mentioned that it crashes even if you remove the delete.. so that is probably not the issue. A difference between direct and queued connection is the order of events. Direct means that the slot function will run immediately when you call "emit" (like a function call) whereas queued signals get put onto the events queue and are handled sequentially. Its hard to say how this affects your code without seeing the updateFilter() function (or where you emit is called) - maybe you can post that as well? Also you should probably disconnect before you delete (last 2 lines) – code_fodder Jul 26 '16 at 08:46
  • this code sample can't work in any possible way. deteLater or delete this is still invalid sine progress bar is never shown. It doesn't have a parent, `show` is not invoked and its lifetime never reaches any event loop so even it if is shown it can't invoke paintEvent so it could be presented to user. – Marek R Jul 26 '16 at 08:56
  • @MarekR: The comment after new QProgressBar describes that the progress bar is correctly setup. Of course it has a parent, is added as a permanent widget to the status bar and is visible. I just skipped that code because it is in my opinion not relevant to the problem. – OregonGhost Jul 26 '16 at 09:02
  • @code_fodder: Added the code. Maybe you can see something there. I also thought about disconnecting the queue before the delete, but forgot because not deleting wouldn't change anything about the crash. – OregonGhost Jul 26 '16 at 09:07
  • I just discovered that with the working solution, the progress bar is not actually updated (i.e. the GUI just hangs), even though the signal is correctly emitted and there are no connection errors. I guess I'd have to put the filtering code into a separate thread to make this better. – OregonGhost Jul 26 '16 at 09:11
  • Actually, that sounds about right... queued will not get a chance to run until your function is finished and by then you delete it anyway. I think you still need to show more... can you expand on "put into status bar etc" and show function invalidate()? Also, as a quick test you could try running the QProgressBar on its own in a loop e.g. `for (int i = 0; i < 100; i++){emit progress(i); QThread::msleep(100);}` - you will have to add a signal "progress" (or called whatever) and connect that, but otherwise this should only take a few minutes to try out... – code_fodder Jul 26 '16 at 09:37
  • invalidate is the QSortFilterProxyModel's invalidate, so nothing to show. I just created the progress bar as a child of the statusbar, added it with addPermanentWidget and that's all. When loading files to display, this works as intended, even though that's also in the main thread. I now just put filtering into a thread, making updating work as expected most of the time. Sometimes, however, filtering is done in both the thread pool thread and the main thread, doubling the time it takes. And I thought multithreading in Qt is easy... – OregonGhost Jul 26 '16 at 10:22

0 Answers0