The problem is that you are modifying the project in the processAlgorithm() method of your custom processing script. Processing algorithms are run in a background thread by default. Manually adding layers created in a background thread to the project (which lives in the main thread) inside the processAlgorithm() method will 100% cause the issues you describe.
The best solution is to use an output parameter e.g. QgsProcessingParameterVectorDestination which you can pass to the 'OUTPUT' parameter of the child algorithm, and let the processing framework take care of properly handling the resulting layer.
Note that you can set a default value for the output parameter so that the widget will be pre-filled with your 'hard-coded' path.
from qgis.PyQt.QtCore import QCoreApplication, QVariant
from qgis.core import (QgsProcessing, QgsProcessingAlgorithm, QgsProcessingParameterVectorLayer,
QgsProcessingParameterVectorDestination)
import processing
class ExAlgo(QgsProcessingAlgorithm):
INPUT = 'INPUT'
OUTPUT = 'OUTPUT'
def __init__(self):
super().__init__()
def name(self):
return "exalgo"
def displayName(self):
return "Example script"
def group(self):
return "Examples"
def groupId(self):
return "examples"
def shortHelpString(self):
return "Example script without logic"
def helpUrl(self):
return "https://qgis.org"
def createInstance(self):
return type(self)()
def initAlgorithm(self, config=None):
self.addParameter(QgsProcessingParameterVectorLayer(
self.INPUT,
"Input layer",
[QgsProcessing.TypeVectorPolygon]))
self.addParameter(QgsProcessingParameterVectorDestination(
self.OUTPUT,
'Output layer',
QgsProcessing.TypeVectorPolygon,
defaultValue=r'C:\Path\To\Some_folder\test_layer.gpkg'))
def processAlgorithm(self, parameters, context, feedback):
input_layer = self.parameterAsVectorLayer(parameters, self.INPUT, context)
alg_params = {
'FIELDS': [],
'INPUT': input_layer,
'OUTPUT': parameters[self.OUTPUT]
}
result = processing.run('native:dissolve',
alg_params,
context=context,
feedback=feedback,
is_child_algorithm=True)
dest_id = result['OUTPUT']
if context.willLoadLayerOnCompletion(dest_id):
details = context.layerToLoadOnCompletionDetails(dest_id)
details.name = 'test layer'
details.forceName = True
return {'OUTPUT': dest_id}
If you want to rename the output layer, instead of having the default e.g. ('Dissolved') you can use this approach:
from qgis.PyQt.QtCore import QCoreApplication, QVariant
from qgis.core import (QgsProcessing, QgsProcessingAlgorithm, QgsProcessingParameterVectorLayer,
QgsProcessingParameterVectorDestination)
import processing
class ExAlgo(QgsProcessingAlgorithm):
INPUT = 'INPUT'
OUTPUT = 'OUTPUT'
def __init__(self):
super().__init__()
def name(self):
return "exalgo"
def displayName(self):
return "Example script"
def group(self):
return "Examples"
def groupId(self):
return "examples"
def shortHelpString(self):
return "Example script without logic"
def helpUrl(self):
return "https://qgis.org"
def createInstance(self):
return type(self)()
def initAlgorithm(self, config=None):
self.addParameter(QgsProcessingParameterVectorLayer(
self.INPUT,
"Input layer",
[QgsProcessing.TypeVectorPolygon]))
self.addParameter(QgsProcessingParameterVectorDestination(
self.OUTPUT,
'Output layer',
QgsProcessing.TypeVectorPolygon,
defaultValue=r'C:\Path\To\Some_folder\test_layer.gpkg'))
def processAlgorithm(self, parameters, context, feedback):
input_layer = self.parameterAsVectorLayer(parameters, self.INPUT, context)
alg_params = {
'FIELDS': [],
'INPUT': input_layer,
'OUTPUT': parameters[self.OUTPUT]
}
result = processing.run('native:dissolve',
alg_params,
context=context,
feedback=feedback,
is_child_algorithm=True)
dest_id = result['OUTPUT']
if context.willLoadLayerOnCompletion(dest_id):
details = context.layerToLoadOnCompletionDetails(dest_id)
details.name = 'test layer'
details.forceName = True
return {'OUTPUT': dest_id}
If you really want to hard code the loading of an output layer, you can do it like this without manually adding it to the project with the problematic project.addMapLayer() call. You should also declare the output in the initAlgorithm() method e.g.
self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, "Output layer"))
*Note: if you want a permanent file output, make sure you pass a hardcoded output path to the 'OUTPUT' parameter of the dissolve child algorithm.
from qgis.PyQt.QtCore import QCoreApplication, QVariant
from qgis.core import (QgsProcessing, QgsProcessingAlgorithm, QgsProcessingParameterVectorLayer,
QgsProcessingContext, QgsFeatureRequest, QgsProcessingOutputVectorLayer)
import processing
class ExAlgo(QgsProcessingAlgorithm):
INPUT = 'INPUT'
OUTPUT = 'OUTPUT'
def __init__(self):
super().__init__()
def name(self):
return "exalgo"
def displayName(self):
return "Example script"
def group(self):
return "Examples"
def groupId(self):
return "examples"
def shortHelpString(self):
return "Example script without logic"
def helpUrl(self):
return "https://qgis.org"
def createInstance(self):
return type(self)()
def initAlgorithm(self, config=None):
self.addParameter(QgsProcessingParameterVectorLayer(
self.INPUT,
"Input layer",
[QgsProcessing.TypeVectorPolygon]))
self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, "Output layer"))
def processAlgorithm(self, parameters, context, feedback):
input_layer = self.parameterAsVectorLayer(parameters, self.INPUT, context)
alg_params = {
'FIELDS': [],
'INPUT': input_layer,
'OUTPUT': r'C:\Example\Path\Some_folder\test_layer.gpkg'
}
result = processing.run('native:dissolve',
alg_params,
context=context,
feedback=feedback,
is_child_algorithm=True)
dest_id = result['OUTPUT']
details = QgsProcessingContext.LayerDetails('test layer', context.project(), 'test layer')
details.forceName = True
context.addLayerToLoadOnCompletion(dest_id, details)
output_layer = context.getMapLayer(dest_id)
return {'OUTPUT': output_layer}
And finally, if you don't want to implement any of the above options and just want to make your existing code work, you can disable threading in your algorithm by overriding your algorithm's flags() method:
def flags(self):
return Qgis.ProcessingAlgorithmFlag.NoThreading
Note that prior to QGIS 3.36, this would be:
def flags(self):
return QgsProcessingAlgorithm.FlagNoThreading
See my answer to a similar question here:
QGIS Processing script causes intermittent crashes, visual gaps in layer tree, and delayed layer visibility