[{"data":1,"prerenderedAt":2109},["ShallowReactive",2],{"doc:\u002Fqgis-plugin-development\u002Fbackground-tasks-and-plugin-performance\u002Frun-background-task-with-qgstask-pyqgis":3},{"id":4,"title":5,"body":6,"description":2098,"extension":2099,"meta":2100,"navigation":259,"path":2105,"seo":2106,"stem":2107,"__hash__":2108},"docs\u002Fqgis-plugin-development\u002Fbackground-tasks-and-plugin-performance\u002Frun-background-task-with-qgstask-pyqgis\u002Findex.md","Run a Long Task with QgsTask in PyQGIS",{"type":7,"value":8,"toc":2085},"minimark",[9,13,21,30,184,189,211,215,683,701,705,708,817,837,853,857,863,1021,1035,1116,1120,1123,1305,1330,1334,1337,1678,1695,1705,1788,1792,1798,1877,1881,1971,1975,1990,1994,2004,2018,2030,2039,2052,2056,2081],[10,11,5],"h1",{"id":12},"run-a-long-task-with-qgstask-in-pyqgis",[14,15,16,20],"p",{},[17,18,19],"code",{},"QgsTask"," is QGIS's answer to a plugin that locks the window. It gives you a managed worker thread, an entry in the task manager with a progress bar and a cancel button, and a guaranteed callback back on the main thread when the work finishes. What it does not give you is protection from the two mistakes everyone makes first: touching a project layer from the worker, and letting the task object be garbage-collected before it starts.",[14,22,23,24,29],{},"This recipe belongs to ",[25,26,28],"a",{"href":27},"\u002Fqgis-plugin-development\u002Fbackground-tasks-and-plugin-performance\u002F","Background Tasks and Plugin Performance",". It covers the subclass, the thread-safe snapshot, returning a result to the main thread, running a Processing algorithm as a task, and chaining dependent tasks.",[14,31,32],{},[33,34,39,43,47,54,71,80,90,96,105,110,114,117,122,128,132,135,142,146,150,155,159,163,166,169,175,180],"svg",{"viewBox":35,"role":36,"ariaLabel":37,"xmlns":38},"0 0 760 296","img","Lifecycle of a QgsTask from construction on the main thread, through queueing in the task manager, execution of run on a worker thread, to the finished callback back on the main thread","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[40,41,42],"title",{},"The lifecycle of a QgsTask",[44,45,46],"desc",{},"A task is constructed on the main thread with a snapshot of its inputs, then handed to the task manager which queues it. The manager calls run on a worker thread, where progress is reported and cancellation is checked. When run returns, the manager calls finished back on the main thread with the boolean result, and that is where the user interface may be updated.",[48,49],"rect",{"x":50,"y":50,"width":51,"height":52,"fill":53},"0","760","296","#f6f3ea",[55,56,57],"defs",{},[58,59,66],"marker",{"id":60,"viewBox":61,"refX":62,"refY":63,"markerWidth":64,"markerHeight":64,"orient":65},"lifeArrow","0 0 10 10","8","5","7","auto-start-reverse",[67,68],"path",{"d":69,"fill":70},"M0 0 L10 5 L0 10 z","#2f3b35",[72,73,79],"text",{"x":74,"y":75,"style":76,"fill":77,"textAnchor":78},"380","28","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","#17211d","middle","Two of these boxes are on the main thread and one is not",[48,81],{"x":82,"y":83,"width":84,"height":85,"rx":86,"fill":87,"stroke":88,"style":89},"16","52","728","76","10","#eff3ff","#2563eb","stroke-width:2",[72,91,95],{"x":92,"y":93,"style":94,"fill":88,"textAnchor":78},"86","78","text-anchor:middle;font-size:11px;font-weight:bold;font-family:sans-serif","main thread",[48,97],{"x":98,"y":99,"width":98,"height":100,"rx":101,"fill":102,"stroke":103,"style":104},"164","66","48","6","#fffdf7","#59645f","stroke-width:1.5",[72,106,109],{"x":107,"y":92,"style":108,"fill":70,"textAnchor":78},"246","text-anchor:middle;font-size:11px;font-family:sans-serif","__init__ · take the",[72,111,113],{"x":107,"y":112,"style":108,"fill":70,"textAnchor":78},"104","snapshot, keep a ref",[48,115],{"x":116,"y":99,"width":98,"height":100,"rx":101,"fill":102,"stroke":103,"style":104},"352",[72,118,121],{"x":119,"y":120,"style":108,"fill":70,"textAnchor":78},"434","94","addTask() — queued",[48,123],{"x":124,"y":99,"width":125,"height":100,"rx":101,"fill":126,"stroke":127,"style":89},"540","188","#edf8e9","#15803d",[72,129,131],{"x":130,"y":92,"style":108,"fill":70,"textAnchor":78},"634","finished(result)",[72,133,134],{"x":130,"y":112,"style":108,"fill":127,"textAnchor":78},"safe to touch the UI",[48,136],{"x":82,"y":137,"width":84,"height":138,"rx":86,"fill":139,"stroke":140,"style":141},"160","88","#eef7f4","#0f766e","stroke-width:2.5",[72,143,145],{"x":92,"y":144,"style":94,"fill":140,"textAnchor":78},"186","worker thread",[48,147],{"x":98,"y":148,"width":116,"height":149,"rx":101,"fill":102,"stroke":140,"style":89},"176","56",[72,151,154],{"x":152,"y":153,"style":108,"fill":70,"textAnchor":78},"340","198","run() — the actual work",[72,156,158],{"x":152,"y":157,"style":108,"fill":70,"textAnchor":78},"218","setProgress() · isCanceled() · return True or False",[48,160],{"x":124,"y":148,"width":125,"height":149,"rx":101,"fill":161,"stroke":162,"style":89},"#fdf2e2","#b91c1c",[72,164,165],{"x":130,"y":153,"style":108,"fill":162,"textAnchor":78},"no widgets here",[72,167,168],{"x":130,"y":157,"style":108,"fill":162,"textAnchor":78},"no project layers here",[170,171],"line",{"x1":119,"y1":172,"x2":74,"y2":173,"stroke":70,"style":174},"118","170","stroke-width:2;marker-end:url(#lifeArrow)",[170,176],{"x1":177,"y1":148,"x2":178,"y2":179,"stroke":70,"style":174},"450","600","120",[72,181,183],{"x":74,"y":182,"style":108,"fill":103,"textAnchor":78},"278","Anything computed in run() reaches the interface only through the object's own attributes, read in finished()",[185,186,188],"h2",{"id":187},"prerequisites","Prerequisites",[190,191,192,200,208],"ul",{},[193,194,195,199],"li",{},[196,197,198],"strong",{},"QGIS 3.34 LTR"," (bundled Python 3.12) or newer.",[193,201,202,203,207],{},"A plugin skeleton — see ",[25,204,206],{"href":205},"\u002Fqgis-plugin-development\u002Fplugin-boilerplate-structure\u002F","Plugin Boilerplate and Structure",".",[193,209,210],{},"A piece of work that genuinely takes more than a second or two. Below that, a background task adds complexity for no perceptible gain.",[185,212,214],{"id":213},"subclass-qgstask","Subclass QgsTask",[216,217,222],"pre",{"className":218,"code":219,"language":220,"meta":221,"style":221},"language-python shiki shiki-themes github-dark","from qgis.core import (\n    QgsTask, QgsApplication, QgsFeatureRequest, QgsMessageLog, Qgis,\n)\n\nclass AreaSumTask(QgsTask):\n    \"\"\"Sum polygon areas without blocking the interface.\"\"\"\n\n    def __init__(self, description, source):\n        super().__init__(description, QgsTask.CanCancel)\n        self.source = source\n        self.total_area = 0.0\n        self.exception = None\n\n    def run(self):\n        try:\n            count = self.source.featureCount() or 1\n            for index, feature in enumerate(self.source.getFeatures()):\n                if self.isCanceled():\n                    return False\n                self.total_area += feature.geometry().area()\n                if index % 500 == 0:\n                    self.setProgress(100 * index \u002F count)\n        except Exception as exc:\n            self.exception = exc\n            return False\n        return True\n\n    def finished(self, result):\n        if self.exception is not None:\n            QgsMessageLog.logMessage(f\"failed: {self.exception}\", \"MyPlugin\", Qgis.Critical)\n        elif result:\n            QgsMessageLog.logMessage(f\"total area {self.total_area:,.0f}\", \"MyPlugin\", Qgis.Success)\n        else:\n            QgsMessageLog.logMessage(\"cancelled\", \"MyPlugin\", Qgis.Info)\n","python","",[17,223,224,242,248,254,261,279,286,291,304,319,334,347,360,365,376,385,405,428,439,448,462,484,507,522,535,543,552,557,568,589,622,631,660,668],{"__ignoreMap":221},[225,226,228,232,236,239],"span",{"class":170,"line":227},1,[225,229,231],{"class":230},"snl16","from",[225,233,235],{"class":234},"s95oV"," qgis.core ",[225,237,238],{"class":230},"import",[225,240,241],{"class":234}," (\n",[225,243,245],{"class":170,"line":244},2,[225,246,247],{"class":234},"    QgsTask, QgsApplication, QgsFeatureRequest, QgsMessageLog, Qgis,\n",[225,249,251],{"class":170,"line":250},3,[225,252,253],{"class":234},")\n",[225,255,257],{"class":170,"line":256},4,[225,258,260],{"emptyLinePlaceholder":259},true,"\n",[225,262,264,267,271,274,276],{"class":170,"line":263},5,[225,265,266],{"class":230},"class",[225,268,270],{"class":269},"svObZ"," AreaSumTask",[225,272,273],{"class":234},"(",[225,275,19],{"class":269},[225,277,278],{"class":234},"):\n",[225,280,282],{"class":170,"line":281},6,[225,283,285],{"class":284},"sU2Wk","    \"\"\"Sum polygon areas without blocking the interface.\"\"\"\n",[225,287,289],{"class":170,"line":288},7,[225,290,260],{"emptyLinePlaceholder":259},[225,292,294,297,301],{"class":170,"line":293},8,[225,295,296],{"class":230},"    def",[225,298,300],{"class":299},"sDLfK"," __init__",[225,302,303],{"class":234},"(self, description, source):\n",[225,305,307,310,313,316],{"class":170,"line":306},9,[225,308,309],{"class":299},"        super",[225,311,312],{"class":234},"().",[225,314,315],{"class":299},"__init__",[225,317,318],{"class":234},"(description, QgsTask.CanCancel)\n",[225,320,322,325,328,331],{"class":170,"line":321},10,[225,323,324],{"class":299},"        self",[225,326,327],{"class":234},".source ",[225,329,330],{"class":230},"=",[225,332,333],{"class":234}," source\n",[225,335,337,339,342,344],{"class":170,"line":336},11,[225,338,324],{"class":299},[225,340,341],{"class":234},".total_area ",[225,343,330],{"class":230},[225,345,346],{"class":299}," 0.0\n",[225,348,350,352,355,357],{"class":170,"line":349},12,[225,351,324],{"class":299},[225,353,354],{"class":234},".exception ",[225,356,330],{"class":230},[225,358,359],{"class":299}," None\n",[225,361,363],{"class":170,"line":362},13,[225,364,260],{"emptyLinePlaceholder":259},[225,366,368,370,373],{"class":170,"line":367},14,[225,369,296],{"class":230},[225,371,372],{"class":269}," run",[225,374,375],{"class":234},"(self):\n",[225,377,379,382],{"class":170,"line":378},15,[225,380,381],{"class":230},"        try",[225,383,384],{"class":234},":\n",[225,386,388,391,393,396,399,402],{"class":170,"line":387},16,[225,389,390],{"class":234},"            count ",[225,392,330],{"class":230},[225,394,395],{"class":299}," self",[225,397,398],{"class":234},".source.featureCount() ",[225,400,401],{"class":230},"or",[225,403,404],{"class":299}," 1\n",[225,406,408,411,414,417,420,422,425],{"class":170,"line":407},17,[225,409,410],{"class":230},"            for",[225,412,413],{"class":234}," index, feature ",[225,415,416],{"class":230},"in",[225,418,419],{"class":299}," enumerate",[225,421,273],{"class":234},[225,423,424],{"class":299},"self",[225,426,427],{"class":234},".source.getFeatures()):\n",[225,429,431,434,436],{"class":170,"line":430},18,[225,432,433],{"class":230},"                if",[225,435,395],{"class":299},[225,437,438],{"class":234},".isCanceled():\n",[225,440,442,445],{"class":170,"line":441},19,[225,443,444],{"class":230},"                    return",[225,446,447],{"class":299}," False\n",[225,449,451,454,456,459],{"class":170,"line":450},20,[225,452,453],{"class":299},"                self",[225,455,341],{"class":234},[225,457,458],{"class":230},"+=",[225,460,461],{"class":234}," feature.geometry().area()\n",[225,463,465,467,470,473,476,479,482],{"class":170,"line":464},21,[225,466,433],{"class":230},[225,468,469],{"class":234}," index ",[225,471,472],{"class":230},"%",[225,474,475],{"class":299}," 500",[225,477,478],{"class":230}," ==",[225,480,481],{"class":299}," 0",[225,483,384],{"class":234},[225,485,487,490,493,496,499,501,504],{"class":170,"line":486},22,[225,488,489],{"class":299},"                    self",[225,491,492],{"class":234},".setProgress(",[225,494,495],{"class":299},"100",[225,497,498],{"class":230}," *",[225,500,469],{"class":234},[225,502,503],{"class":230},"\u002F",[225,505,506],{"class":234}," count)\n",[225,508,510,513,516,519],{"class":170,"line":509},23,[225,511,512],{"class":230},"        except",[225,514,515],{"class":299}," Exception",[225,517,518],{"class":230}," as",[225,520,521],{"class":234}," exc:\n",[225,523,525,528,530,532],{"class":170,"line":524},24,[225,526,527],{"class":299},"            self",[225,529,354],{"class":234},[225,531,330],{"class":230},[225,533,534],{"class":234}," exc\n",[225,536,538,541],{"class":170,"line":537},25,[225,539,540],{"class":230},"            return",[225,542,447],{"class":299},[225,544,546,549],{"class":170,"line":545},26,[225,547,548],{"class":230},"        return",[225,550,551],{"class":299}," True\n",[225,553,555],{"class":170,"line":554},27,[225,556,260],{"emptyLinePlaceholder":259},[225,558,560,562,565],{"class":170,"line":559},28,[225,561,296],{"class":230},[225,563,564],{"class":269}," finished",[225,566,567],{"class":234},"(self, result):\n",[225,569,571,574,576,578,581,584,587],{"class":170,"line":570},29,[225,572,573],{"class":230},"        if",[225,575,395],{"class":299},[225,577,354],{"class":234},[225,579,580],{"class":230},"is",[225,582,583],{"class":230}," not",[225,585,586],{"class":299}," None",[225,588,384],{"class":234},[225,590,592,595,598,601,604,607,610,613,616,619],{"class":170,"line":591},30,[225,593,594],{"class":234},"            QgsMessageLog.logMessage(",[225,596,597],{"class":230},"f",[225,599,600],{"class":284},"\"failed: ",[225,602,603],{"class":299},"{self",[225,605,606],{"class":234},".exception",[225,608,609],{"class":299},"}",[225,611,612],{"class":284},"\"",[225,614,615],{"class":234},", ",[225,617,618],{"class":284},"\"MyPlugin\"",[225,620,621],{"class":234},", Qgis.Critical)\n",[225,623,625,628],{"class":170,"line":624},31,[225,626,627],{"class":230},"        elif",[225,629,630],{"class":234}," result:\n",[225,632,634,636,638,641,643,646,649,651,653,655,657],{"class":170,"line":633},32,[225,635,594],{"class":234},[225,637,597],{"class":230},[225,639,640],{"class":284},"\"total area ",[225,642,603],{"class":299},[225,644,645],{"class":234},".total_area",[225,647,648],{"class":230},":,.0f",[225,650,609],{"class":299},[225,652,612],{"class":284},[225,654,615],{"class":234},[225,656,618],{"class":284},[225,658,659],{"class":234},", Qgis.Success)\n",[225,661,663,666],{"class":170,"line":662},33,[225,664,665],{"class":230},"        else",[225,667,384],{"class":234},[225,669,671,673,676,678,680],{"class":170,"line":670},34,[225,672,594],{"class":234},[225,674,675],{"class":284},"\"cancelled\"",[225,677,615],{"class":234},[225,679,618],{"class":284},[225,681,682],{"class":234},", Qgis.Info)\n",[14,684,685,688,689,692,693,696,697,700],{},[196,686,687],{},"Breakdown:"," ",[17,690,691],{},"QgsTask.CanCancel"," is what puts a cancel button next to the progress entry; without it the flag can never be set. Catching exceptions inside ",[17,694,695],{},"run()"," and storing them is necessary because an exception escaping a worker thread does not propagate anywhere useful — it disappears, and the task simply reports failure with no explanation. ",[17,698,699],{},"finished()"," distinguishes three outcomes, and treating \"cancelled\" separately from \"failed\" matters to the user: one is their decision and the other is a bug. Progress is set every 500 features rather than every feature because the signal crosses a thread boundary and is not free.",[185,702,704],{"id":703},"hand-over-something-the-task-can-own","Hand over something the task can own",[14,706,707],{},"The task must not read a layer that lives in the project. Take a snapshot on the main thread first.",[216,709,711],{"className":218,"code":710,"language":220,"meta":221,"style":221},"from qgis.core import QgsProject, QgsFeatureRequest\n\nlayer = QgsProject.instance().mapLayersByName(\"parcels\")[0]\n\nrequest = QgsFeatureRequest().setSubsetOfAttributes([])   # geometry only\nsnapshot = layer.materialize(request)\n\ntask = AreaSumTask(\"Summing parcel areas\", snapshot)\nQgsApplication.taskManager().addTask(task)\nself.task = task                                          # keep a reference\n",[17,712,713,724,728,749,753,767,777,781,797,802],{"__ignoreMap":221},[225,714,715,717,719,721],{"class":170,"line":227},[225,716,231],{"class":230},[225,718,235],{"class":234},[225,720,238],{"class":230},[225,722,723],{"class":234}," QgsProject, QgsFeatureRequest\n",[225,725,726],{"class":170,"line":244},[225,727,260],{"emptyLinePlaceholder":259},[225,729,730,733,735,738,741,744,746],{"class":170,"line":250},[225,731,732],{"class":234},"layer ",[225,734,330],{"class":230},[225,736,737],{"class":234}," QgsProject.instance().mapLayersByName(",[225,739,740],{"class":284},"\"parcels\"",[225,742,743],{"class":234},")[",[225,745,50],{"class":299},[225,747,748],{"class":234},"]\n",[225,750,751],{"class":170,"line":256},[225,752,260],{"emptyLinePlaceholder":259},[225,754,755,758,760,763],{"class":170,"line":263},[225,756,757],{"class":234},"request ",[225,759,330],{"class":230},[225,761,762],{"class":234}," QgsFeatureRequest().setSubsetOfAttributes([])   ",[225,764,766],{"class":765},"sjoCn","# geometry only\n",[225,768,769,772,774],{"class":170,"line":281},[225,770,771],{"class":234},"snapshot ",[225,773,330],{"class":230},[225,775,776],{"class":234}," layer.materialize(request)\n",[225,778,779],{"class":170,"line":288},[225,780,260],{"emptyLinePlaceholder":259},[225,782,783,786,788,791,794],{"class":170,"line":293},[225,784,785],{"class":234},"task ",[225,787,330],{"class":230},[225,789,790],{"class":234}," AreaSumTask(",[225,792,793],{"class":284},"\"Summing parcel areas\"",[225,795,796],{"class":234},", snapshot)\n",[225,798,799],{"class":170,"line":306},[225,800,801],{"class":234},"QgsApplication.taskManager().addTask(task)\n",[225,803,804,806,809,811,814],{"class":170,"line":321},[225,805,424],{"class":299},[225,807,808],{"class":234},".task ",[225,810,330],{"class":230},[225,812,813],{"class":234}," task                                          ",[225,815,816],{"class":765},"# keep a reference\n",[14,818,819,688,821,824,825,829,830,833,834,836],{},[196,820,687],{},[17,822,823],{},"materialize()"," runs on the main thread and returns an independent in-memory layer holding exactly the features the request selects — the task owns it, so no cross-thread access occurs. Requesting no attributes makes the copy dramatically cheaper when only geometry is needed, which is the same lever described in ",[25,826,828],{"href":827},"\u002Fqgis-plugin-development\u002Fbackground-tasks-and-plugin-performance\u002Fspeed-up-feature-iteration-with-qgsfeaturerequest-pyqgis\u002F","Speed Up Feature Iteration with QgsFeatureRequest",". The last line is not decoration: ",[17,831,832],{},"addTask()"," does not keep Python's reference alive, and a task collected between construction and execution silently never runs. Assigning it to ",[17,835,424],{}," — or any object that outlives the function — is the fix.",[14,838,839,840,843,844,688,847,688,850,852],{},"For a very large layer where copying is not acceptable, pass the layer's ",[17,841,842],{},"source()"," string and construct a fresh ",[17,845,846],{},"QgsVectorLayer",[196,848,849],{},"inside",[17,851,695],{},". That layer belongs to the worker thread and is safe.",[185,854,856],{"id":855},"return-a-result-to-the-main-thread","Return a result to the main thread",[14,858,859,860,862],{},"Results travel as attributes on the task, read in ",[17,861,699],{},", or as a custom signal.",[216,864,866],{"className":218,"code":865,"language":220,"meta":221,"style":221},"from qgis.PyQt.QtCore import pyqtSignal\n\nclass BufferTask(QgsTask):\n    resultReady = pyqtSignal(object)\n\n    def run(self):\n        self.output_path = \"\u002Ftmp\u002Fbuffered.gpkg\"\n        # … write the output …\n        return True\n\n    def finished(self, result):\n        if result:\n            self.resultReady.emit(self.output_path)\n\ntask = BufferTask(\"Buffering\")\ntask.resultReady.connect(lambda path: iface.addVectorLayer(path, \"Buffered\", \"ogr\"))\nQgsApplication.taskManager().addTask(task)\n",[17,867,868,880,884,897,912,916,924,936,941,947,951,959,965,977,981,995,1017],{"__ignoreMap":221},[225,869,870,872,875,877],{"class":170,"line":227},[225,871,231],{"class":230},[225,873,874],{"class":234}," qgis.PyQt.QtCore ",[225,876,238],{"class":230},[225,878,879],{"class":234}," pyqtSignal\n",[225,881,882],{"class":170,"line":244},[225,883,260],{"emptyLinePlaceholder":259},[225,885,886,888,891,893,895],{"class":170,"line":250},[225,887,266],{"class":230},[225,889,890],{"class":269}," BufferTask",[225,892,273],{"class":234},[225,894,19],{"class":269},[225,896,278],{"class":234},[225,898,899,902,904,907,910],{"class":170,"line":256},[225,900,901],{"class":234},"    resultReady ",[225,903,330],{"class":230},[225,905,906],{"class":234}," pyqtSignal(",[225,908,909],{"class":299},"object",[225,911,253],{"class":234},[225,913,914],{"class":170,"line":263},[225,915,260],{"emptyLinePlaceholder":259},[225,917,918,920,922],{"class":170,"line":281},[225,919,296],{"class":230},[225,921,372],{"class":269},[225,923,375],{"class":234},[225,925,926,928,931,933],{"class":170,"line":288},[225,927,324],{"class":299},[225,929,930],{"class":234},".output_path ",[225,932,330],{"class":230},[225,934,935],{"class":284}," \"\u002Ftmp\u002Fbuffered.gpkg\"\n",[225,937,938],{"class":170,"line":293},[225,939,940],{"class":765},"        # … write the output …\n",[225,942,943,945],{"class":170,"line":306},[225,944,548],{"class":230},[225,946,551],{"class":299},[225,948,949],{"class":170,"line":321},[225,950,260],{"emptyLinePlaceholder":259},[225,952,953,955,957],{"class":170,"line":336},[225,954,296],{"class":230},[225,956,564],{"class":269},[225,958,567],{"class":234},[225,960,961,963],{"class":170,"line":349},[225,962,573],{"class":230},[225,964,630],{"class":234},[225,966,967,969,972,974],{"class":170,"line":362},[225,968,527],{"class":299},[225,970,971],{"class":234},".resultReady.emit(",[225,973,424],{"class":299},[225,975,976],{"class":234},".output_path)\n",[225,978,979],{"class":170,"line":367},[225,980,260],{"emptyLinePlaceholder":259},[225,982,983,985,987,990,993],{"class":170,"line":378},[225,984,785],{"class":234},[225,986,330],{"class":230},[225,988,989],{"class":234}," BufferTask(",[225,991,992],{"class":284},"\"Buffering\"",[225,994,253],{"class":234},[225,996,997,1000,1003,1006,1009,1011,1014],{"class":170,"line":387},[225,998,999],{"class":234},"task.resultReady.connect(",[225,1001,1002],{"class":230},"lambda",[225,1004,1005],{"class":234}," path: iface.addVectorLayer(path, ",[225,1007,1008],{"class":284},"\"Buffered\"",[225,1010,615],{"class":234},[225,1012,1013],{"class":284},"\"ogr\"",[225,1015,1016],{"class":234},"))\n",[225,1018,1019],{"class":170,"line":407},[225,1020,801],{"class":234},[14,1022,1023,688,1025,1027,1028,1030,1031,1034],{},[196,1024,687],{},[17,1026,699],{}," already runs on the main thread, so emitting from there delivers the signal safely to any connected slot. Adding the layer to the project happens in the slot, never in ",[17,1029,695],{}," — ",[17,1032,1033],{},"QgsProject"," is main-thread property. Emitting a path rather than a layer object is deliberate: the layer is then constructed on the thread that will own it.",[14,1036,1037],{},[33,1038,1041,1044,1047,1050,1053,1057,1062,1066,1070,1074,1077,1080,1084,1087,1090,1093,1096,1100,1104,1107,1110,1113],{"viewBox":1039,"role":36,"ariaLabel":1040,"xmlns":38},"0 0 760 262","Three ways to hand a result back, ranked: writing to a file path and loading it on the main thread, storing plain data on the task, and passing a layer object across threads which is unsafe",[40,1042,1043],{},"Three ways to return a result, only two of them safe",[44,1045,1046],{},"Writing the output to disk in the worker and loading it on the main thread is safe and works for any size. Storing plain Python data such as numbers or lists on the task and reading it in finished is safe for small results. Constructing a layer in the worker and adding it to the project from there is unsafe and may crash.",[48,1048],{"x":50,"y":50,"width":51,"height":1049,"fill":53},"262",[72,1051,1052],{"x":74,"y":75,"style":76,"fill":77,"textAnchor":78},"Send data across the boundary, not objects",[48,1054],{"x":82,"y":100,"width":1055,"height":1056,"rx":86,"fill":126,"stroke":127,"style":141},"236","184",[72,1058,1061],{"x":1059,"y":85,"style":1060,"fill":127,"textAnchor":78},"134","text-anchor:middle;font-size:12px;font-weight:bold;font-family:sans-serif","write, then load",[72,1063,1065],{"x":1059,"y":1064,"style":108,"fill":70,"textAnchor":78},"106","run() writes a GeoPackage",[72,1067,1069],{"x":1059,"y":1068,"style":108,"fill":70,"textAnchor":78},"128","finished() loads the path",[72,1071,1073],{"x":1059,"y":1072,"style":108,"fill":127,"textAnchor":78},"158","any size, fully safe",[72,1075,1076],{"x":1059,"y":125,"style":108,"fill":70,"textAnchor":78},"the default choice",[48,1078],{"x":1079,"y":100,"width":1055,"height":1056,"rx":86,"fill":87,"stroke":88,"style":141},"266",[72,1081,1083],{"x":1082,"y":85,"style":1060,"fill":88,"textAnchor":78},"384","plain data on self",[72,1085,1086],{"x":1082,"y":1064,"style":108,"fill":70,"textAnchor":78},"numbers, lists, dicts",[72,1088,1089],{"x":1082,"y":1068,"style":108,"fill":70,"textAnchor":78},"read in finished()",[72,1091,1092],{"x":1082,"y":1072,"style":108,"fill":88,"textAnchor":78},"safe while it stays small",[72,1094,1095],{"x":1082,"y":125,"style":108,"fill":70,"textAnchor":78},"good for summaries",[48,1097],{"x":1098,"y":100,"width":1099,"height":1056,"rx":86,"fill":161,"stroke":162,"style":141},"516","228",[72,1101,1103],{"x":1102,"y":85,"style":1060,"fill":162,"textAnchor":78},"630","a layer, from run()",[72,1105,1106],{"x":1102,"y":1064,"style":108,"fill":70,"textAnchor":78},"addMapLayer() in the worker",[72,1108,1109],{"x":1102,"y":1068,"style":108,"fill":70,"textAnchor":78},"or a project layer captured",[72,1111,1112],{"x":1102,"y":1072,"style":108,"fill":162,"textAnchor":78},"crashes, eventually",[72,1114,1115],{"x":1102,"y":125,"style":108,"fill":70,"textAnchor":78},"never do this",[185,1117,1119],{"id":1118},"run-a-processing-algorithm-as-a-task","Run a Processing algorithm as a task",[14,1121,1122],{},"For a single algorithm there is a purpose-built wrapper — no subclass required.",[216,1124,1126],{"className":218,"code":1125,"language":220,"meta":221,"style":221},"from qgis.core import QgsApplication, QgsProcessingAlgRunnerTask, QgsProcessingContext, QgsProcessingFeedback\nfrom qgis import processing\n\ncontext = QgsProcessingContext()\nfeedback = QgsProcessingFeedback()\nalgorithm = QgsApplication.processingRegistry().algorithmById(\"native:buffer\")\n\ndef on_complete(ok, results):\n    if ok:\n        iface.addVectorLayer(results[\"OUTPUT\"], \"Buffered\", \"ogr\")\n\ntask = QgsProcessingAlgRunnerTask(\n    algorithm,\n    {\"INPUT\": layer.source(), \"DISTANCE\": 50, \"OUTPUT\": \"\u002Ftmp\u002Fbuffered.gpkg\"},\n    context,\n    feedback,\n)\ntask.executed.connect(on_complete)\nQgsApplication.taskManager().addTask(task)\n",[17,1127,1128,1139,1151,1155,1165,1175,1190,1194,1205,1213,1232,1236,1245,1250,1282,1287,1292,1296,1301],{"__ignoreMap":221},[225,1129,1130,1132,1134,1136],{"class":170,"line":227},[225,1131,231],{"class":230},[225,1133,235],{"class":234},[225,1135,238],{"class":230},[225,1137,1138],{"class":234}," QgsApplication, QgsProcessingAlgRunnerTask, QgsProcessingContext, QgsProcessingFeedback\n",[225,1140,1141,1143,1146,1148],{"class":170,"line":244},[225,1142,231],{"class":230},[225,1144,1145],{"class":234}," qgis ",[225,1147,238],{"class":230},[225,1149,1150],{"class":234}," processing\n",[225,1152,1153],{"class":170,"line":250},[225,1154,260],{"emptyLinePlaceholder":259},[225,1156,1157,1160,1162],{"class":170,"line":256},[225,1158,1159],{"class":234},"context ",[225,1161,330],{"class":230},[225,1163,1164],{"class":234}," QgsProcessingContext()\n",[225,1166,1167,1170,1172],{"class":170,"line":263},[225,1168,1169],{"class":234},"feedback ",[225,1171,330],{"class":230},[225,1173,1174],{"class":234}," QgsProcessingFeedback()\n",[225,1176,1177,1180,1182,1185,1188],{"class":170,"line":281},[225,1178,1179],{"class":234},"algorithm ",[225,1181,330],{"class":230},[225,1183,1184],{"class":234}," QgsApplication.processingRegistry().algorithmById(",[225,1186,1187],{"class":284},"\"native:buffer\"",[225,1189,253],{"class":234},[225,1191,1192],{"class":170,"line":288},[225,1193,260],{"emptyLinePlaceholder":259},[225,1195,1196,1199,1202],{"class":170,"line":293},[225,1197,1198],{"class":230},"def",[225,1200,1201],{"class":269}," on_complete",[225,1203,1204],{"class":234},"(ok, results):\n",[225,1206,1207,1210],{"class":170,"line":306},[225,1208,1209],{"class":230},"    if",[225,1211,1212],{"class":234}," ok:\n",[225,1214,1215,1218,1221,1224,1226,1228,1230],{"class":170,"line":321},[225,1216,1217],{"class":234},"        iface.addVectorLayer(results[",[225,1219,1220],{"class":284},"\"OUTPUT\"",[225,1222,1223],{"class":234},"], ",[225,1225,1008],{"class":284},[225,1227,615],{"class":234},[225,1229,1013],{"class":284},[225,1231,253],{"class":234},[225,1233,1234],{"class":170,"line":336},[225,1235,260],{"emptyLinePlaceholder":259},[225,1237,1238,1240,1242],{"class":170,"line":349},[225,1239,785],{"class":234},[225,1241,330],{"class":230},[225,1243,1244],{"class":234}," QgsProcessingAlgRunnerTask(\n",[225,1246,1247],{"class":170,"line":362},[225,1248,1249],{"class":234},"    algorithm,\n",[225,1251,1252,1255,1258,1261,1264,1267,1270,1272,1274,1276,1279],{"class":170,"line":367},[225,1253,1254],{"class":234},"    {",[225,1256,1257],{"class":284},"\"INPUT\"",[225,1259,1260],{"class":234},": layer.source(), ",[225,1262,1263],{"class":284},"\"DISTANCE\"",[225,1265,1266],{"class":234},": ",[225,1268,1269],{"class":299},"50",[225,1271,615],{"class":234},[225,1273,1220],{"class":284},[225,1275,1266],{"class":234},[225,1277,1278],{"class":284},"\"\u002Ftmp\u002Fbuffered.gpkg\"",[225,1280,1281],{"class":234},"},\n",[225,1283,1284],{"class":170,"line":378},[225,1285,1286],{"class":234},"    context,\n",[225,1288,1289],{"class":170,"line":387},[225,1290,1291],{"class":234},"    feedback,\n",[225,1293,1294],{"class":170,"line":407},[225,1295,253],{"class":234},[225,1297,1298],{"class":170,"line":430},[225,1299,1300],{"class":234},"task.executed.connect(on_complete)\n",[225,1302,1303],{"class":170,"line":441},[225,1304,801],{"class":234},[14,1306,1307,688,1309,1312,1313,1316,1317,1320,1321,1324,1325,1329],{},[196,1308,687],{},[17,1310,1311],{},"QgsProcessingAlgRunnerTask"," handles the threading, progress and cancellation for any registered algorithm, so a plugin that mostly orchestrates Processing needs no custom task class at all. Passing ",[17,1314,1315],{},"layer.source()"," rather than the layer object keeps the input thread-safe. The ",[17,1318,1319],{},"executed"," signal fires on the main thread with a success flag and the results dictionary — the same dictionary ",[17,1322,1323],{},"processing.run()"," would have returned, as described in ",[25,1326,1328],{"href":1327},"\u002Fspatial-data-processing-automation\u002Fbatch-processing-with-pyqgis\u002Frun-processing-algorithm-from-script\u002F","Run a Processing Algorithm from a Script",". The context must outlive the task, so keep a reference to it as well.",[185,1331,1333],{"id":1332},"run-processing-inside-your-own-task","Run Processing inside your own task",[14,1335,1336],{},"A task that wraps several algorithms — rather than one — needs its own feedback object so progress and cancellation reach them.",[216,1338,1340],{"className":218,"code":1339,"language":220,"meta":221,"style":221},"from qgis.core import QgsProcessingFeedback, QgsProcessingContext\nimport processing\n\nclass TaskFeedback(QgsProcessingFeedback):\n    def __init__(self, task):\n        super().__init__(False)\n        self._task = task\n\n    def isCanceled(self):\n        return self._task.isCanceled()\n\n    def setProgress(self, progress):\n        self._task.setProgress(progress)\n\n\nclass PipelineTask(QgsTask):\n    def run(self):\n        feedback = TaskFeedback(self)\n        context = QgsProcessingContext()\n\n        reprojected = processing.run(\"native:reprojectlayer\", {\n            \"INPUT\": self.source_uri, \"TARGET_CRS\": \"EPSG:27700\",\n            \"OUTPUT\": \"TEMPORARY_OUTPUT\",\n        }, context=context, feedback=feedback)[\"OUTPUT\"]\n\n        if self.isCanceled():\n            return False\n\n        self.output_path = processing.run(\"native:buffer\", {\n            \"INPUT\": reprojected, \"DISTANCE\": 25,\n            \"OUTPUT\": self.destination,\n        }, context=context, feedback=feedback)[\"OUTPUT\"]\n        return True\n",[17,1341,1342,1353,1359,1363,1377,1386,1401,1413,1417,1426,1435,1439,1449,1456,1460,1464,1477,1485,1499,1508,1512,1528,1551,1563,1589,1593,1601,1607,1611,1625,1641,1652,1672],{"__ignoreMap":221},[225,1343,1344,1346,1348,1350],{"class":170,"line":227},[225,1345,231],{"class":230},[225,1347,235],{"class":234},[225,1349,238],{"class":230},[225,1351,1352],{"class":234}," QgsProcessingFeedback, QgsProcessingContext\n",[225,1354,1355,1357],{"class":170,"line":244},[225,1356,238],{"class":230},[225,1358,1150],{"class":234},[225,1360,1361],{"class":170,"line":250},[225,1362,260],{"emptyLinePlaceholder":259},[225,1364,1365,1367,1370,1372,1375],{"class":170,"line":256},[225,1366,266],{"class":230},[225,1368,1369],{"class":269}," TaskFeedback",[225,1371,273],{"class":234},[225,1373,1374],{"class":269},"QgsProcessingFeedback",[225,1376,278],{"class":234},[225,1378,1379,1381,1383],{"class":170,"line":263},[225,1380,296],{"class":230},[225,1382,300],{"class":299},[225,1384,1385],{"class":234},"(self, task):\n",[225,1387,1388,1390,1392,1394,1396,1399],{"class":170,"line":281},[225,1389,309],{"class":299},[225,1391,312],{"class":234},[225,1393,315],{"class":299},[225,1395,273],{"class":234},[225,1397,1398],{"class":299},"False",[225,1400,253],{"class":234},[225,1402,1403,1405,1408,1410],{"class":170,"line":288},[225,1404,324],{"class":299},[225,1406,1407],{"class":234},"._task ",[225,1409,330],{"class":230},[225,1411,1412],{"class":234}," task\n",[225,1414,1415],{"class":170,"line":293},[225,1416,260],{"emptyLinePlaceholder":259},[225,1418,1419,1421,1424],{"class":170,"line":306},[225,1420,296],{"class":230},[225,1422,1423],{"class":269}," isCanceled",[225,1425,375],{"class":234},[225,1427,1428,1430,1432],{"class":170,"line":321},[225,1429,548],{"class":230},[225,1431,395],{"class":299},[225,1433,1434],{"class":234},"._task.isCanceled()\n",[225,1436,1437],{"class":170,"line":336},[225,1438,260],{"emptyLinePlaceholder":259},[225,1440,1441,1443,1446],{"class":170,"line":349},[225,1442,296],{"class":230},[225,1444,1445],{"class":269}," setProgress",[225,1447,1448],{"class":234},"(self, progress):\n",[225,1450,1451,1453],{"class":170,"line":362},[225,1452,324],{"class":299},[225,1454,1455],{"class":234},"._task.setProgress(progress)\n",[225,1457,1458],{"class":170,"line":367},[225,1459,260],{"emptyLinePlaceholder":259},[225,1461,1462],{"class":170,"line":378},[225,1463,260],{"emptyLinePlaceholder":259},[225,1465,1466,1468,1471,1473,1475],{"class":170,"line":387},[225,1467,266],{"class":230},[225,1469,1470],{"class":269}," PipelineTask",[225,1472,273],{"class":234},[225,1474,19],{"class":269},[225,1476,278],{"class":234},[225,1478,1479,1481,1483],{"class":170,"line":407},[225,1480,296],{"class":230},[225,1482,372],{"class":269},[225,1484,375],{"class":234},[225,1486,1487,1490,1492,1495,1497],{"class":170,"line":430},[225,1488,1489],{"class":234},"        feedback ",[225,1491,330],{"class":230},[225,1493,1494],{"class":234}," TaskFeedback(",[225,1496,424],{"class":299},[225,1498,253],{"class":234},[225,1500,1501,1504,1506],{"class":170,"line":441},[225,1502,1503],{"class":234},"        context ",[225,1505,330],{"class":230},[225,1507,1164],{"class":234},[225,1509,1510],{"class":170,"line":450},[225,1511,260],{"emptyLinePlaceholder":259},[225,1513,1514,1517,1519,1522,1525],{"class":170,"line":464},[225,1515,1516],{"class":234},"        reprojected ",[225,1518,330],{"class":230},[225,1520,1521],{"class":234}," processing.run(",[225,1523,1524],{"class":284},"\"native:reprojectlayer\"",[225,1526,1527],{"class":234},", {\n",[225,1529,1530,1533,1535,1537,1540,1543,1545,1548],{"class":170,"line":486},[225,1531,1532],{"class":284},"            \"INPUT\"",[225,1534,1266],{"class":234},[225,1536,424],{"class":299},[225,1538,1539],{"class":234},".source_uri, ",[225,1541,1542],{"class":284},"\"TARGET_CRS\"",[225,1544,1266],{"class":234},[225,1546,1547],{"class":284},"\"EPSG:27700\"",[225,1549,1550],{"class":234},",\n",[225,1552,1553,1556,1558,1561],{"class":170,"line":509},[225,1554,1555],{"class":284},"            \"OUTPUT\"",[225,1557,1266],{"class":234},[225,1559,1560],{"class":284},"\"TEMPORARY_OUTPUT\"",[225,1562,1550],{"class":234},[225,1564,1565,1568,1572,1574,1577,1580,1582,1585,1587],{"class":170,"line":524},[225,1566,1567],{"class":234},"        }, ",[225,1569,1571],{"class":1570},"s9osk","context",[225,1573,330],{"class":230},[225,1575,1576],{"class":234},"context, ",[225,1578,1579],{"class":1570},"feedback",[225,1581,330],{"class":230},[225,1583,1584],{"class":234},"feedback)[",[225,1586,1220],{"class":284},[225,1588,748],{"class":234},[225,1590,1591],{"class":170,"line":537},[225,1592,260],{"emptyLinePlaceholder":259},[225,1594,1595,1597,1599],{"class":170,"line":545},[225,1596,573],{"class":230},[225,1598,395],{"class":299},[225,1600,438],{"class":234},[225,1602,1603,1605],{"class":170,"line":554},[225,1604,540],{"class":230},[225,1606,447],{"class":299},[225,1608,1609],{"class":170,"line":559},[225,1610,260],{"emptyLinePlaceholder":259},[225,1612,1613,1615,1617,1619,1621,1623],{"class":170,"line":570},[225,1614,324],{"class":299},[225,1616,930],{"class":234},[225,1618,330],{"class":230},[225,1620,1521],{"class":234},[225,1622,1187],{"class":284},[225,1624,1527],{"class":234},[225,1626,1627,1629,1632,1634,1636,1639],{"class":170,"line":591},[225,1628,1532],{"class":284},[225,1630,1631],{"class":234},": reprojected, ",[225,1633,1263],{"class":284},[225,1635,1266],{"class":234},[225,1637,1638],{"class":299},"25",[225,1640,1550],{"class":234},[225,1642,1643,1645,1647,1649],{"class":170,"line":624},[225,1644,1555],{"class":284},[225,1646,1266],{"class":234},[225,1648,424],{"class":299},[225,1650,1651],{"class":234},".destination,\n",[225,1653,1654,1656,1658,1660,1662,1664,1666,1668,1670],{"class":170,"line":633},[225,1655,1567],{"class":234},[225,1657,1571],{"class":1570},[225,1659,330],{"class":230},[225,1661,1576],{"class":234},[225,1663,1579],{"class":1570},[225,1665,330],{"class":230},[225,1667,1584],{"class":234},[225,1669,1220],{"class":284},[225,1671,748],{"class":234},[225,1673,1674,1676],{"class":170,"line":662},[225,1675,548],{"class":230},[225,1677,551],{"class":299},[14,1679,1680,1682,1683,1686,1687,1690,1691,1694],{},[196,1681,687],{}," Overriding ",[17,1684,1685],{},"isCanceled()"," to delegate to the task is what makes the cancel button stop an algorithm mid-run — without it, pressing cancel sets a flag the algorithm never consults, and the user waits out a step they asked to abandon. Delegating ",[17,1688,1689],{},"setProgress"," gives the task's progress bar the current algorithm's progress, which is honest for one step and misleading for several; scaling it per stage (",[17,1692,1693],{},"self.setProgress(50 + progress \u002F 2)"," for the second of two) is the small refinement that makes a multi-stage bar mean something.",[14,1696,1697,1698,1701,1702,1704],{},"Passing the same ",[17,1699,1700],{},"QgsProcessingContext"," through every call lets layers created by one algorithm be found by the next, and — importantly for a task — keeps them alive for the duration. A context created inside the loop would let intermediate layers be collected while a later step still refers to them. Checking ",[17,1703,1685],{}," between the algorithms costs nothing and stops the chain at the first opportunity.",[14,1706,1707],{},[33,1708,1711,1714,1717,1720,1727,1730,1734,1739,1743,1746,1750,1753,1757,1760,1763,1766,1772,1774,1778,1781,1785],{"viewBox":1709,"role":36,"ariaLabel":1710,"xmlns":38},"0 0 760 244","A feedback object bridging a task and the algorithms it runs, carrying cancellation downward and progress upward across two chained algorithm calls",[40,1712,1713],{},"The feedback object bridges the task and the algorithms",[44,1715,1716],{},"The task owns a feedback object whose isCanceled delegates to the task and whose setProgress reports upward. Both chained algorithm calls receive that same object and the same processing context, so a cancel request reaches whichever algorithm is currently running and intermediate layers stay alive across the calls.",[48,1718],{"x":50,"y":50,"width":51,"height":1719,"fill":53},"244",[55,1721,1722],{},[58,1723,1725],{"id":1724,"viewBox":61,"refX":62,"refY":63,"markerWidth":64,"markerHeight":64,"orient":65},"fbArrow",[67,1726],{"d":69,"fill":70},[72,1728,1729],{"x":74,"y":75,"style":76,"fill":77,"textAnchor":78},"Cancellation only reaches an algorithm that was given the feedback",[48,1731],{"x":1732,"y":138,"width":148,"height":1733,"rx":62,"fill":139,"stroke":140,"style":141},"20","72",[72,1735,1738],{"x":1736,"y":1737,"style":1060,"fill":140,"textAnchor":78},"108","116","PipelineTask",[72,1740,1742],{"x":1736,"y":1741,"style":108,"fill":70,"textAnchor":78},"138","isCanceled · setProgress",[48,1744],{"x":1745,"y":138,"width":148,"height":1733,"rx":62,"fill":87,"stroke":88,"style":141},"240",[72,1747,1749],{"x":1748,"y":1737,"style":1060,"fill":88,"textAnchor":78},"328","TaskFeedback",[72,1751,1752],{"x":1748,"y":1741,"style":108,"fill":70,"textAnchor":78},"delegates both ways",[48,1754],{"x":1755,"y":149,"width":1756,"height":83,"rx":62,"fill":102,"stroke":103,"style":89},"460","280",[72,1758,1759],{"x":178,"y":92,"style":108,"fill":70,"textAnchor":78},"native:reprojectlayer",[48,1761],{"x":1755,"y":1762,"width":1756,"height":83,"rx":62,"fill":102,"stroke":103,"style":89},"140",[72,1764,1765],{"x":178,"y":173,"style":108,"fill":70,"textAnchor":78},"native:buffer",[170,1767],{"x1":1768,"y1":1769,"x2":1770,"y2":1769,"stroke":70,"style":1771},"196","112","234","stroke-width:2;marker-end:url(#fbArrow)",[170,1773],{"x1":1770,"y1":1762,"x2":1768,"y2":1762,"stroke":70,"style":1771},[170,1775],{"x1":1776,"y1":1736,"x2":1777,"y2":92,"stroke":70,"style":1771},"416","454",[170,1779],{"x1":1776,"y1":1762,"x2":1777,"y2":1780,"stroke":70,"style":1771},"166",[72,1782,1784],{"x":74,"y":1783,"style":108,"fill":103,"textAnchor":78},"216","One shared QgsProcessingContext keeps intermediate layers alive across the chain",[72,1786,1787],{"x":74,"y":1770,"style":108,"fill":103,"textAnchor":78},"Scale progress per stage so a two-step bar does not run 0–100 twice",[185,1789,1791],{"id":1790},"qgis-version-compatibility","QGIS version compatibility",[14,1793,1794,1795,1797],{},"The examples target ",[196,1796,198],{}," (Python 3.12).",[1799,1800,1801,1817],"table",{},[1802,1803,1804],"thead",{},[1805,1806,1807,1811,1814],"tr",{},[1808,1809,1810],"th",{},"QGIS version",[1808,1812,1813],{},"Python",[1808,1815,1816],{},"Notes",[1818,1819,1820,1839,1849,1860],"tbody",{},[1805,1821,1822,1826,1829],{},[1823,1824,1825],"td",{},"3.22 LTR",[1823,1827,1828],{},"3.9",[1823,1830,1831,615,1833,1835,1836,1838],{},[17,1832,19],{},[17,1834,823],{}," and ",[17,1837,1311],{}," all present and behave as shown.",[1805,1840,1841,1844,1846],{},[1823,1842,1843],{},"3.28 LTR",[1823,1845,1828],{},[1823,1847,1848],{},"Identical API.",[1805,1850,1851,1854,1857],{},[1823,1852,1853],{},"3.34 LTR",[1823,1855,1856],{},"3.12",[1823,1858,1859],{},"Baseline for this page.",[1805,1861,1862,1865,1867],{},[1823,1863,1864],{},"3.40 \u002F 3.44",[1823,1866,1856],{},[1823,1868,1869,1872,1873,1876],{},[17,1870,1871],{},"QgsTask.Flag"," enum members are scoped (",[17,1874,1875],{},"QgsTask.Flag.CanCancel","); the unscoped spelling still works.",[185,1878,1880],{"id":1879},"troubleshooting","Troubleshooting",[190,1882,1883,1895,1911,1921,1941,1959],{},[193,1884,1885,1888,1889,1891,1892,1894],{},[196,1886,1887],{},"The task never runs."," No Python reference was kept. Assign it to ",[17,1890,424],{}," or a module-level variable until ",[17,1893,699],{}," fires.",[193,1896,1897,1900,1901,1903,1904,1907,1908,1910],{},[196,1898,1899],{},"QGIS crashes partway through."," Something in ",[17,1902,695],{}," touched a project layer, a widget or ",[17,1905,1906],{},"iface",". Move it to ",[17,1909,699],{}," or snapshot it before starting.",[193,1912,1913,688,1916,1918,1919,207],{},[196,1914,1915],{},"Cancel does nothing.",[17,1917,1685],{}," is not checked inside the loop, or the task was created without ",[17,1920,691],{},[193,1922,1923,688,1926,1929,1930,1933,1934,1937,1938,207],{},[196,1924,1925],{},"The progress bar jumps to 100% and sits there.",[17,1927,1928],{},"setProgress()"," is called with a value derived from a count that was wrong — often ",[17,1931,1932],{},"featureCount()"," returning ",[17,1935,1936],{},"-1"," for a provider that does not know. Guard with ",[17,1939,1940],{},"max(count, 1)",[193,1942,1943,1948,1949,1951,1952,503,1955,1958],{},[196,1944,1945,1947],{},[17,1946,699],{}," reports failure with no message."," An exception escaped ",[17,1950,695],{},". Wrap the body in ",[17,1953,1954],{},"try",[17,1956,1957],{},"except"," and store the exception as shown.",[193,1960,1961,1964,1965,1967,1968,1970],{},[196,1962,1963],{},"Results are empty although the work succeeded."," The result was stored on a local variable rather than on ",[17,1966,424],{},", so ",[17,1969,699],{}," never saw it.",[185,1972,1974],{"id":1973},"conclusion","Conclusion",[14,1976,1977,1978,1980,1981,1983,1984,1986,1987,1989],{},"A correct ",[17,1979,19],{}," is four things: a subclass whose ",[17,1982,695],{}," touches nothing owned by the main thread, a snapshot handed over before it starts, a reference kept until it finishes, and a ",[17,1985,699],{}," that applies the result. When the work is a single Processing algorithm, ",[17,1988,1311],{}," provides all of it without a subclass.",[185,1991,1993],{"id":1992},"frequently-asked-questions","Frequently Asked Questions",[14,1995,1996,1999,2000,2003],{},[196,1997,1998],{},"How do I make one task wait for another?","\nCall ",[17,2001,2002],{},"task.addSubTask(child, [dependencies])",", or add the dependency list when adding to the manager. The manager will not start a task until its dependencies have completed successfully.",[14,2005,2006,2009,2010,2013,2014,207],{},[196,2007,2008],{},"Can I update a progress bar in my own dialog?","\nYes — connect to the task's ",[17,2011,2012],{},"progressChanged"," signal, which is delivered on the main thread. That pattern is covered in ",[25,2015,2017],{"href":2016},"\u002Fqgis-plugin-development\u002Fbackground-tasks-and-plugin-performance\u002Fshow-plugin-progress-and-cancellation-pyqgis\u002F","Show Progress and Support Cancellation in a QGIS Plugin",[14,2019,2020,2026,2027,2029],{},[196,2021,2022,2023,2025],{},"Is ",[17,2024,823],{}," expensive?","\nIt copies features into memory, so it costs roughly what one full read costs. Narrow the request first; for very large layers, rebuild the layer from its source inside ",[17,2028,695],{}," instead.",[14,2031,2032,2035,2036,2038],{},[196,2033,2034],{},"What happens if the user closes the plugin dialog mid-task?","\nThe task keeps running — it belongs to the manager, not the dialog. Guard ",[17,2037,699],{}," against a deleted widget, or cancel the task from the dialog's close handler.",[14,2040,2041,2044,2045,2047,2048,207],{},[196,2042,2043],{},"Can I write to a database from a task?","\nYes, provided the connection is created inside ",[17,2046,695],{},". Do not share a layer or connection object created on the main thread — see ",[25,2049,2051],{"href":2050},"\u002Fspatial-data-processing-automation\u002Fpostgis-and-database-workflows\u002Fappend-features-to-postgis-table-pyqgis\u002F","Append Features to a PostGIS Table in PyQGIS",[185,2053,2055],{"id":2054},"related","Related",[190,2057,2058,2063,2067,2071,2077],{},[193,2059,2060,2062],{},[25,2061,28],{"href":27}," — the guide this recipe belongs to",[193,2064,2065],{},[25,2066,2017],{"href":2016},[193,2068,2069],{},[25,2070,828],{"href":827},[193,2072,2073],{},[25,2074,2076],{"href":2075},"\u002Fqgis-plugin-development\u002Fcustom-map-tools-and-canvas-interaction\u002Fconnect-layer-signals-pyqgis\u002F","Connect Layer Signals in PyQGIS",[193,2078,2079],{},[25,2080,206],{"href":205},[2082,2083,2084],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":221,"searchDepth":244,"depth":244,"links":2086},[2087,2088,2089,2090,2091,2092,2093,2094,2095,2096,2097],{"id":187,"depth":244,"text":188},{"id":213,"depth":244,"text":214},{"id":703,"depth":244,"text":704},{"id":855,"depth":244,"text":856},{"id":1118,"depth":244,"text":1119},{"id":1332,"depth":244,"text":1333},{"id":1790,"depth":244,"text":1791},{"id":1879,"depth":244,"text":1880},{"id":1973,"depth":244,"text":1974},{"id":1992,"depth":244,"text":1993},{"id":2054,"depth":244,"text":2055},"Move slow plugin work onto a background thread with QgsTask — subclass it correctly, hand over a thread-safe snapshot, return results to the main thread, and avoid the reference bug that stops tasks running.","md",{"slug":2101,"type":2102,"breadcrumb":2103,"datePublished":2104,"dateModified":2104},"run-background-task-with-qgstask-pyqgis","article","Run a QgsTask","2026-08-10","\u002Fqgis-plugin-development\u002Fbackground-tasks-and-plugin-performance\u002Frun-background-task-with-qgstask-pyqgis",{"title":5,"description":2098},"qgis-plugin-development\u002Fbackground-tasks-and-plugin-performance\u002Frun-background-task-with-qgstask-pyqgis\u002Findex","aGE9wVPZXNO_26L0FdvQs3IP7YkHvhWfmElYsAh0JK0",1786401337553]