Skip to content

Commit

Permalink
Merge pull request #4204 from nyalldawson/cancel_without_blocking
Browse files Browse the repository at this point in the history
Don't block when canceling canvas render jobs
  • Loading branch information
nyalldawson authored Mar 2, 2017
2 parents e9dee40 + 6243f78 commit 1752d44
Show file tree
Hide file tree
Showing 13 changed files with 120 additions and 11 deletions.
1 change: 1 addition & 0 deletions python/core/qgsmaprenderercustompainterjob.sip
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class QgsMapRendererCustomPainterJob : QgsMapRendererJob

virtual void start();
virtual void cancel();
virtual void cancelWithoutBlocking();
virtual void waitForFinished();
virtual bool isActive() const;
virtual bool usedCachedLabels() const;
Expand Down
2 changes: 2 additions & 0 deletions python/core/qgsmaprendererjob.sip
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ class QgsMapRendererJob : QObject
//! Does nothing if the rendering is not active.
virtual void cancel() = 0;

virtual void cancelWithoutBlocking() = 0;

//! Block until the job has finished.
virtual void waitForFinished() = 0;

Expand Down
1 change: 1 addition & 0 deletions python/core/qgsmaprendererparalleljob.sip
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class QgsMapRendererParallelJob : QgsMapRendererQImageJob

virtual void start();
virtual void cancel();
virtual void cancelWithoutBlocking();
virtual void waitForFinished();
virtual bool isActive() const;
virtual bool usedCachedLabels() const;
Expand Down
1 change: 1 addition & 0 deletions python/core/qgsmaprenderersequentialjob.sip
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class QgsMapRendererSequentialJob : QgsMapRendererQImageJob

virtual void start();
virtual void cancel();
virtual void cancelWithoutBlocking();
virtual void waitForFinished();
virtual bool isActive() const;
virtual bool usedCachedLabels() const;
Expand Down
26 changes: 18 additions & 8 deletions src/core/qgsmaprenderercustompainterjob.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,14 +109,7 @@ void QgsMapRendererCustomPainterJob::cancel()

QgsDebugMsg( "QPAINTER canceling" );
disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererCustomPainterJob::futureFinished );

mLabelJob.context.setRenderingStopped( true );
for ( LayerRenderJobs::iterator it = mLayerJobs.begin(); it != mLayerJobs.end(); ++it )
{
it->context.setRenderingStopped( true );
if ( it->renderer && it->renderer->feedback() )
it->renderer->feedback()->cancel();
}
cancelWithoutBlocking();

QTime t;
t.start();
Expand All @@ -130,6 +123,23 @@ void QgsMapRendererCustomPainterJob::cancel()
QgsDebugMsg( "QPAINTER canceled" );
}

void QgsMapRendererCustomPainterJob::cancelWithoutBlocking()
{
if ( !isActive() )
{
QgsDebugMsg( "QPAINTER not running!" );
return;
}

mLabelJob.context.setRenderingStopped( true );
for ( LayerRenderJobs::iterator it = mLayerJobs.begin(); it != mLayerJobs.end(); ++it )
{
it->context.setRenderingStopped( true );
if ( it->renderer && it->renderer->feedback() )
it->renderer->feedback()->cancel();
}
}

void QgsMapRendererCustomPainterJob::waitForFinished()
{
if ( !isActive() )
Expand Down
1 change: 1 addition & 0 deletions src/core/qgsmaprenderercustompainterjob.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class CORE_EXPORT QgsMapRendererCustomPainterJob : public QgsMapRendererJob

virtual void start() override;
virtual void cancel() override;
virtual void cancelWithoutBlocking() override;
virtual void waitForFinished() override;
virtual bool isActive() const override;
virtual bool usedCachedLabels() const override;
Expand Down
8 changes: 8 additions & 0 deletions src/core/qgsmaprendererjob.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,13 @@ class CORE_EXPORT QgsMapRendererJob : public QObject
//! Does nothing if the rendering is not active.
virtual void cancel() = 0;

/**
* Triggers cancelation of the rendering job without blocking. The render job will continue
* to operate until it is able to cancel, at which stage the finished() signal will be emitted.
* Does nothing if the rendering is not active.
*/
virtual void cancelWithoutBlocking() = 0;

//! Block until the job has finished.
virtual void waitForFinished() = 0;

Expand Down Expand Up @@ -296,6 +303,7 @@ class CORE_EXPORT QgsMapRendererQImageJob : public QgsMapRendererJob

//! Get a preview/resulting image
virtual QImage renderedImage() = 0;

};


Expand Down
22 changes: 22 additions & 0 deletions src/core/qgsmaprendererparalleljob.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,28 @@ void QgsMapRendererParallelJob::cancel()
Q_ASSERT( mStatus == Idle );
}

void QgsMapRendererParallelJob::cancelWithoutBlocking()
{
if ( !isActive() )
return;

QgsDebugMsg( QString( "PARALLEL cancel at status %1" ).arg( mStatus ) );

mLabelJob.context.setRenderingStopped( true );
for ( LayerRenderJobs::iterator it = mLayerJobs.begin(); it != mLayerJobs.end(); ++it )
{
it->context.setRenderingStopped( true );
if ( it->renderer && it->renderer->feedback() )
it->renderer->feedback()->cancel();
}

if ( mStatus == RenderingLayers )
{
disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderLayersFinished );
connect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderingFinished );
}
}

void QgsMapRendererParallelJob::waitForFinished()
{
if ( !isActive() )
Expand Down
1 change: 1 addition & 0 deletions src/core/qgsmaprendererparalleljob.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class CORE_EXPORT QgsMapRendererParallelJob : public QgsMapRendererQImageJob

virtual void start() override;
virtual void cancel() override;
virtual void cancelWithoutBlocking() override;
virtual void waitForFinished() override;
virtual bool isActive() const override;

Expand Down
9 changes: 9 additions & 0 deletions src/core/qgsmaprenderersequentialjob.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,15 @@ void QgsMapRendererSequentialJob::cancel()
Q_ASSERT( !mInternalJob && !mPainter );
}

void QgsMapRendererSequentialJob::cancelWithoutBlocking()
{
if ( !isActive() )
return;

QgsDebugMsg( "sequential - cancel internal" );
mInternalJob->cancelWithoutBlocking();
}

void QgsMapRendererSequentialJob::waitForFinished()
{
if ( !isActive() )
Expand Down
1 change: 1 addition & 0 deletions src/core/qgsmaprenderersequentialjob.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class CORE_EXPORT QgsMapRendererSequentialJob : public QgsMapRendererQImageJob

virtual void start() override;
virtual void cancel() override;
virtual void cancelWithoutBlocking() override;
virtual void waitForFinished() override;
virtual bool isActive() const override;

Expand Down
6 changes: 4 additions & 2 deletions src/gui/qgsmapcanvas.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -604,8 +604,10 @@ void QgsMapCanvas::stopRendering()
{
QgsDebugMsg( "CANVAS stop rendering!" );
mJobCanceled = true;
mJob->cancel();
Q_ASSERT( !mJob ); // no need to delete here: already deleted in finished()
disconnect( mJob, &QgsMapRendererJob::finished, this, &QgsMapCanvas::rendererJobFinished );
connect( mJob, &QgsMapRendererQImageJob::finished, mJob, &QgsMapRendererQImageJob::deleteLater );
mJob->cancelWithoutBlocking();
mJob = nullptr;
}
}

Expand Down
52 changes: 51 additions & 1 deletion tests/src/python/test_qgsmaprenderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,14 @@
QgsProject,
QgsFeature,
QgsGeometry,
QgsMapSettings)
QgsMapSettings,
QgsPoint)
from qgis.testing import start_app, unittest
from qgis.PyQt.QtCore import QSize, QThreadPool
from qgis.PyQt.QtGui import QPainter, QImage
from qgis.PyQt.QtTest import QSignalSpy
from random import uniform


app = start_app()

Expand Down Expand Up @@ -378,6 +382,51 @@ def checkLabeledLayerWithBlendModesCannotBeCached(self, job_type):
self.assertFalse(cache.hasCacheImage('_labels_'))
self.assertTrue(job.takeLabelingResults())

def checkCancel(self, job_type):
"""test canceling a render job"""
layer = QgsVectorLayer("Point?field=fldtxt:string",
"layer1", "memory")

# add a ton of random points
for i in range(2000):
x = uniform(5, 25)
y = uniform(25, 45)
g = QgsGeometry.fromPoint(QgsPoint(x, y))
f = QgsFeature()
f.setGeometry(g)
f.initAttributes(1)
layer.dataProvider().addFeatures([f])

settings = QgsMapSettings()
settings.setExtent(QgsRectangle(5, 25, 25, 45))
settings.setOutputSize(QSize(600, 400))
settings.setLayers([layer])

# first try non-blocking cancelWithoutBlocking() call
job = job_type(settings)
finished_spy = QSignalSpy(job.finished)
job.start()

# insta cancel!
job.cancelWithoutBlocking()
# should still be active immediately after
self.assertTrue(job.isActive())

while job.isActive():
app.processEvents()
self.assertEqual(len(finished_spy), 1)

# try blocking cancel() call
job = job_type(settings)
finished_spy = QSignalSpy(job.finished)
job.start()

# insta cancel!
job.cancel()
# should not be active anymore
self.assertFalse(job.isActive())
self.assertEqual(len(finished_spy), 1)

def runRendererChecks(self, renderer):
""" runs all checks on the specified renderer """
self.checkRendererUseCachedLabels(renderer)
Expand All @@ -388,6 +437,7 @@ def runRendererChecks(self, renderer):
self.checkAddingNewNonLabeledLayerKeepsLabelCache(renderer)
self.checkRemovingNonLabeledLayerKeepsLabelCache(renderer)
self.checkLabeledLayerWithBlendModesCannotBeCached(renderer)
self.checkCancel(renderer)

def testParallelRenderer(self):
""" run test suite on QgsMapRendererParallelJob"""
Expand Down

0 comments on commit 1752d44

Please sign in to comment.