Skip to content
Permalink
Browse files

Merge pull request #4204 from nyalldawson/cancel_without_blocking

Don't block when canceling canvas render jobs
  • Loading branch information
nyalldawson committed Mar 2, 2017
2 parents e9dee40 + 6243f78 commit 1752d446e71e678ab6e0e4f8e16b6579f31659d2
@@ -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;
@@ -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;

@@ -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;
@@ -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;
@@ -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();
@@ -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() )
@@ -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;
@@ -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;

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

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

};


@@ -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() )
@@ -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;

@@ -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() )
@@ -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;

@@ -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;
}
}

@@ -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()

@@ -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)
@@ -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"""

0 comments on commit 1752d44

Please sign in to comment.
You can’t perform that action at this time.