Don't use time.sleep(). If you want to zoom to a feature then save the canvas as an image, use QTimer.singleShot().
However, I recommend using one of the QgsMapRendererJob subclasses as per the example below.
This example also uses a custom widget wrapper to allow the user to select the district to export which should solve the problem in your other question.
from processing.gui.wrappers import WidgetWrapper from qgis.PyQt.QtCore import (QCoreApplication, QVariant, Qt) from qgis.PyQt.QtWidgets import (QLabel, QWidget, QGridLayout, QComboBox) from qgis.core import (QgsProcessing, QgsProcessingAlgorithm, QgsProcessingParameterMatrix, QgsProcessingParameterFileDestination, QgsMapLayerProxyModel, QgsFieldProxyModel, QgsMapRendererParallelJob) from qgis.gui import (QgsMapLayerComboBox, QgsFieldComboBox) from qgis.utils import iface import os class AddLayoutTable(QgsProcessingAlgorithm): INPUT_PARAMS = 'INPUT_PARAMS' OUTPUT_PATH = 'OUTPUT_PATH' render = None def __init__(self): super().__init__() def name(self): return "exportdistrict" def displayName(self): return "Export District" def group(self): return "General" def groupId(self): return "general" def shortHelpString(self): return "Export map of selected district." def helpUrl(self): return "https://qgis.org" def createInstance(self): return type(self)() def flags(self): return QgsProcessingAlgorithm.FlagNoThreading def initAlgorithm(self, config=None): input_params = QgsProcessingParameterMatrix(self.INPUT_PARAMS, 'Input Parameters') input_params.setMetadata({'widget_wrapper': {'class': CustomParametersWidget}}) self.addParameter(input_params) self.addParameter(QgsProcessingParameterFileDestination(self.OUTPUT_PATH, 'Save image to', fileFilter='PNG files (*.png)')) def processAlgorithm(self, parameters, context, feedback): # Retrieve the list of parameters returned by the custom widget wrapper input_params_list = self.parameterAsMatrix(parameters, 'INPUT_PARAMS', context) # Access the list items to retrieve each parameter object district_lyr = input_params_list[0] district_fld = input_params_list[1] district_name = input_params_list[2] district_feats = [ft for ft in district_lyr.getFeatures() if ft[district_fld] == district_name] if not district_feats: return {} district_feat = district_feats[0] save_path = self.parameterAsFileOutput(parameters, self.OUTPUT_PATH, context) canvas = iface.mapCanvas() settings = canvas.mapSettings() settings.setDestinationCrs(district_lyr.crs()) extent = district_feat.geometry().boundingBox() extent.grow(0.01) settings.setExtent(extent) self.render = QgsMapRendererParallelJob(settings) self.render.finished.connect(lambda: self.renderFinished(save_path)) # Start the rendering self.render.start() self.render.waitForFinished() return {'Exported Image': save_path} def renderFinished(self, save_path): img = self.render.renderedImage() img.save(save_path, "png") # Widget Wrapper class class CustomParametersWidget(WidgetWrapper): def createWidget(self): self.cpw = CustomWidget() return self.cpw def value(self): # This method gets the parameter values and returns them in a list... # which will be retrieved and parsed in the processAlgorithm() method self.lyr = self.cpw.getLayer() self.fld = self.cpw.getField() self.district = self.cpw.getDistrict() return [self.lyr, self.fld, self.district] # Custom Widget class class CustomWidget(QWidget): def __init__(self): super(CustomWidget, self).__init__() self.lyr_lbl = QLabel('Select District Layer', self) self.lyr_cb = QgsMapLayerComboBox(self) self.fld_lbl = QLabel('Select District Field') self.fld_cb = QgsFieldComboBox(self) self.id_lbl = QLabel('Select District') self.id_cb = QComboBox(self) self.layout = QGridLayout(self) self.layout.addWidget(self.lyr_lbl, 0, 0, 1, 1) self.layout.addWidget(self.lyr_cb, 0, 1, 1, 2) self.layout.addWidget(self.fld_lbl, 1, 0, 1, 1) self.layout.addWidget(self.fld_cb, 1, 1, 1, 2) self.layout.addWidget(self.id_lbl, 2, 0, 1, 1) self.layout.addWidget(self.id_cb, 2, 1, 1, 2) # Set filter on the map layer combobox (here we show only polygon layers) self.lyr_cb.setFilters(QgsMapLayerProxyModel.PolygonLayer) self.fld_cb.setLayer(self.lyr_cb.currentLayer()) # Set filters on field combobox (here we show only string fields) self.fld_cb.setFilters(QgsFieldProxyModel.String) self.lyr_cb.layerChanged.connect(self.layerChanged) self.populateDistricts() self.fld_cb.fieldChanged.connect(self.populateDistricts) def layerChanged(self): self.fld_cb.setLayer(self.lyr_cb.currentLayer()) def populateDistricts(self): self.id_cb.clear() district_lyr = self.lyr_cb.currentLayer() if not district_lyr: return id_fld = self.fld_cb.currentField() fld_idx = district_lyr.fields().lookupField(id_fld) id_vals = district_lyr.uniqueValues(fld_idx) self.id_cb.addItems([str(val) for val in id_vals]) def getLayer(self): return self.lyr_cb.currentLayer() def getField(self): return self.fld_cb.currentField() def getDistrict(self): return self.id_cb.currentText()
The resulting algorithm dialog looks like below, where user can select the layer and the field containing the district names. A third combobox is then populated with the district names found in the layer. These parameters are not dependant on any hard-coded project layer or file path.

I have returned the No Threading flag in this example, but you may get away without it, since we are not actually modifying/zooming the canvas. Do some testing and see if you get any crashes or weird behaviour.