[{"data":1,"prerenderedAt":2049},["ShallowReactive",2],{"doc:\u002Fqgis-plugin-development\u002Fbackground-tasks-and-plugin-performance\u002Fchain-dependent-qgstasks-pyqgis":3},{"id":4,"title":5,"body":6,"description":2038,"extension":2039,"meta":2040,"navigation":256,"path":2045,"seo":2046,"stem":2047,"__hash__":2048},"docs\u002Fqgis-plugin-development\u002Fbackground-tasks-and-plugin-performance\u002Fchain-dependent-qgstasks-pyqgis\u002Findex.md","Chain Dependent QgsTasks in PyQGIS",{"type":7,"value":8,"toc":2023},"minimark",[9,13,22,36,185,190,210,214,224,712,740,744,747,813,1004,1012,1016,1019,1235,1243,1247,1253,1336,1534,1549,1553,1556,1604,1613,1617,1623,1782,1800,1804,1807,1876,1892,1896,1908,1912,1942,1946,1952,1956,1962,1968,1974,1985,1989,2019],[10,11,5],"h1",{"id":12},"chain-dependent-qgstasks-in-pyqgis",[14,15,16,17,21],"p",{},"Real plugin jobs are rarely one step. A sync job downloads data, validates it, then writes it into a layer; an analysis clips inputs, runs three independent calculations, then combines the results. Running each step as its own background task keeps QGIS responsive and shows progress, but the steps have an order: validation cannot start before the download finishes, and the combine step needs all three calculations. ",[18,19,20],"code",{},"QgsTask"," supports this directly with subtasks and dependencies, so the task manager runs work in the right order, in parallel where possible, and cancels the rest when one step fails.",[14,23,24,25,30,31,35],{},"This recipe belongs to ",[26,27,29],"a",{"href":28},"\u002Fqgis-plugin-development\u002Fbackground-tasks-and-plugin-performance\u002F","Background Tasks & Plugin Performance"," and builds on ",[26,32,34],{"href":33},"\u002Fqgis-plugin-development\u002Fbackground-tasks-and-plugin-performance\u002Frun-background-task-with-qgstask-pyqgis\u002F","running a background task with QgsTask",". It chains tasks serially and in parallel, passes results between steps, protects layers that tasks depend on, and handles cancellation.",[14,37,38],{},[39,40,45,49,53,60,77,86,95,101,107,114,119,122,126,129,133,138,143,146,151,155,158,166,168,171,176,178,181],"svg",{"viewBox":41,"role":42,"ariaLabel":43,"xmlns":44},"0 0 760 246","img","A parent task with parallel subtasks feeding a final combine task that depends on all of them","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[46,47,48],"title",{},"A chain of background tasks",[50,51,52],"desc",{},"A parent task download has three parallel subtasks clip A, clip B and clip C, which run at the same time once download finishes. A final combine subtask depends on all three and runs only after they complete. If any step fails, the dependants are cancelled and the parent reports failure. The task manager handles the order; the plugin only declares it.",[54,55],"rect",{"x":56,"y":56,"width":57,"height":58,"fill":59},"0","760","246","#f6f3ea",[61,62,63],"defs",{},[64,65,72],"marker",{"id":66,"viewBox":67,"refX":68,"refY":69,"markerWidth":70,"markerHeight":70,"orient":71},"ctOverviewArrow","0 0 10 10","8","5","7","auto-start-reverse",[73,74],"path",{"d":75,"fill":76},"M0 0 L10 5 L0 10 z","#2f3b35",[78,79,85],"text",{"x":80,"y":81,"style":82,"fill":83,"textAnchor":84},"380","28","text-anchor:middle;font-size:14px;font-family:sans-serif;font-weight:bold","#17211d","middle","Declare the order; the task manager runs it",[54,87],{"x":88,"y":89,"width":90,"height":91,"rx":68,"fill":92,"stroke":93,"style":94},"24","100","150","70","#eef7f4","#0f766e","stroke-width:2",[78,96,100],{"x":97,"y":98,"style":99,"fill":93,"textAnchor":84},"99","128.51","text-anchor:middle;font-size:12.0px;font-family:sans-serif;font-weight:bold","download",[78,102,106],{"x":97,"y":103,"style":104,"fill":105,"textAnchor":84},"149.41","text-anchor:middle;font-size:11.0px;font-family:sans-serif","#59645f","parent",[54,108],{"x":109,"y":110,"width":90,"height":111,"rx":68,"fill":112,"stroke":113,"style":94},"228","40","54","#eff3ff","#2563eb",[78,115,118],{"x":116,"y":117,"style":99,"fill":113,"textAnchor":84},"303","70.96","clip A",[54,120],{"x":109,"y":121,"width":90,"height":111,"rx":68,"fill":112,"stroke":113,"style":94},"108",[78,123,125],{"x":116,"y":124,"style":99,"fill":113,"textAnchor":84},"138.96","clip B",[54,127],{"x":109,"y":128,"width":90,"height":111,"rx":68,"fill":112,"stroke":113,"style":94},"176",[78,130,132],{"x":116,"y":131,"style":99,"fill":113,"textAnchor":84},"206.96","clip C",[54,134],{"x":135,"y":89,"width":90,"height":91,"rx":68,"fill":136,"stroke":137,"style":94},"432","#e8efe6","#15803d",[78,139,142],{"x":140,"y":98,"style":99,"fill":141,"textAnchor":84},"507","#166534","combine",[78,144,145],{"x":140,"y":103,"style":104,"fill":105,"textAnchor":84},"depends on all",[54,147],{"x":148,"y":89,"width":149,"height":91,"rx":68,"fill":150,"stroke":105,"style":94},"616","120","#fffdf7",[78,152,154],{"x":153,"y":98,"style":99,"fill":83,"textAnchor":84},"676","finished",[78,156,157],{"x":153,"y":103,"style":104,"fill":105,"textAnchor":84},"result",[159,160],"line",{"x1":161,"y1":162,"x2":163,"y2":164,"stroke":76,"style":165},"174","135","226","67","stroke-width:1.8;marker-end:url(#ctOverviewArrow)",[159,167],{"x1":161,"y1":162,"x2":163,"y2":162,"stroke":76,"style":165},[159,169],{"x1":161,"y1":162,"x2":163,"y2":170,"stroke":76,"style":165},"203",[159,172],{"x1":173,"y1":164,"x2":174,"y2":175,"stroke":76,"style":165},"378","430","128",[159,177],{"x1":173,"y1":162,"x2":174,"y2":162,"stroke":76,"style":165},[159,179],{"x1":173,"y1":170,"x2":174,"y2":180,"stroke":76,"style":165},"142",[159,182],{"x1":183,"y1":162,"x2":184,"y2":162,"stroke":76,"style":165},"582","614",[186,187,189],"h2",{"id":188},"prerequisites","Prerequisites",[191,192,193,197],"ul",{},[194,195,196],"li",{},"QGIS 3.34 LTR or newer, or the QGIS 4 series.",[194,198,199,200,202,203,206,207,209],{},"Familiarity with subclassing ",[18,201,20],{}," and its ",[18,204,205],{},"run"," and ",[18,208,154],{}," methods.",[186,211,213],{"id":212},"add-serial-subtasks","Add serial subtasks",[14,215,216,217,220,221,223],{},"A task can own subtasks. With ",[18,218,219],{},"ParentDependsOnSubTask",", the parent's own ",[18,222,205],{}," waits until its subtasks complete; serial order between subtasks is expressed with dependencies.",[225,226,231],"pre",{"className":227,"code":228,"language":229,"meta":230,"style":230},"language-python shiki shiki-themes github-dark","from qgis.core import QgsApplication, QgsTask, QgsMessageLog, Qgis\n\nclass Step(QgsTask):\n    def __init__(self, name, work, results):\n        super().__init__(name, QgsTask.CanCancel)\n        self.work, self.results = work, results\n        self.error = None\n\n    def run(self):\n        try:\n            self.results[self.description()] = self.work(self, self.results)\n            return not self.isCanceled()\n        except Exception as e:\n            self.error = e\n            return False\n\n    def finished(self, ok):\n        level = Qgis.Info if ok else Qgis.Warning\n        QgsMessageLog.logMessage(f\"{self.description()}: {'ok' if ok else self.error}\", \"Chain\", level)\n\nresults = {}\ndownload = Step(\"download\", lambda t, r: \"\u002Ftmp\u002Finput.gpkg\", results)\nvalidate = Step(\"validate\", lambda t, r: f\"checked {r['download']}\", results)\nload = Step(\"load\", lambda t, r: f\"loaded after {r['validate']}\", results)\n\nparent = QgsTask.fromFunction(\"Sync job\", lambda task: results)\nparent.addSubTask(download, [], QgsTask.ParentDependsOnSubTask)\nparent.addSubTask(validate, [download], QgsTask.ParentDependsOnSubTask)\nparent.addSubTask(load, [validate], QgsTask.ParentDependsOnSubTask)\nQgsApplication.taskManager().addTask(parent)\n","python","",[18,232,233,251,258,276,289,304,325,338,343,354,363,395,409,424,436,444,449,460,483,538,543,554,582,623,662,667,688,694,700,706],{"__ignoreMap":230},[234,235,237,241,245,248],"span",{"class":159,"line":236},1,[234,238,240],{"class":239},"snl16","from",[234,242,244],{"class":243},"s95oV"," qgis.core ",[234,246,247],{"class":239},"import",[234,249,250],{"class":243}," QgsApplication, QgsTask, QgsMessageLog, Qgis\n",[234,252,254],{"class":159,"line":253},2,[234,255,257],{"emptyLinePlaceholder":256},true,"\n",[234,259,261,264,268,271,273],{"class":159,"line":260},3,[234,262,263],{"class":239},"class",[234,265,267],{"class":266},"svObZ"," Step",[234,269,270],{"class":243},"(",[234,272,20],{"class":266},[234,274,275],{"class":243},"):\n",[234,277,279,282,286],{"class":159,"line":278},4,[234,280,281],{"class":239},"    def",[234,283,285],{"class":284},"sDLfK"," __init__",[234,287,288],{"class":243},"(self, name, work, results):\n",[234,290,292,295,298,301],{"class":159,"line":291},5,[234,293,294],{"class":284},"        super",[234,296,297],{"class":243},"().",[234,299,300],{"class":284},"__init__",[234,302,303],{"class":243},"(name, QgsTask.CanCancel)\n",[234,305,307,310,313,316,319,322],{"class":159,"line":306},6,[234,308,309],{"class":284},"        self",[234,311,312],{"class":243},".work, ",[234,314,315],{"class":284},"self",[234,317,318],{"class":243},".results ",[234,320,321],{"class":239},"=",[234,323,324],{"class":243}," work, results\n",[234,326,328,330,333,335],{"class":159,"line":327},7,[234,329,309],{"class":284},[234,331,332],{"class":243},".error ",[234,334,321],{"class":239},[234,336,337],{"class":284}," None\n",[234,339,341],{"class":159,"line":340},8,[234,342,257],{"emptyLinePlaceholder":256},[234,344,346,348,351],{"class":159,"line":345},9,[234,347,281],{"class":239},[234,349,350],{"class":266}," run",[234,352,353],{"class":243},"(self):\n",[234,355,357,360],{"class":159,"line":356},10,[234,358,359],{"class":239},"        try",[234,361,362],{"class":243},":\n",[234,364,366,369,372,374,377,379,382,385,387,390,392],{"class":159,"line":365},11,[234,367,368],{"class":284},"            self",[234,370,371],{"class":243},".results[",[234,373,315],{"class":284},[234,375,376],{"class":243},".description()] ",[234,378,321],{"class":239},[234,380,381],{"class":284}," self",[234,383,384],{"class":243},".work(",[234,386,315],{"class":284},[234,388,389],{"class":243},", ",[234,391,315],{"class":284},[234,393,394],{"class":243},".results)\n",[234,396,398,401,404,406],{"class":159,"line":397},12,[234,399,400],{"class":239},"            return",[234,402,403],{"class":239}," not",[234,405,381],{"class":284},[234,407,408],{"class":243},".isCanceled()\n",[234,410,412,415,418,421],{"class":159,"line":411},13,[234,413,414],{"class":239},"        except",[234,416,417],{"class":284}," Exception",[234,419,420],{"class":239}," as",[234,422,423],{"class":243}," e:\n",[234,425,427,429,431,433],{"class":159,"line":426},14,[234,428,368],{"class":284},[234,430,332],{"class":243},[234,432,321],{"class":239},[234,434,435],{"class":243}," e\n",[234,437,439,441],{"class":159,"line":438},15,[234,440,400],{"class":239},[234,442,443],{"class":284}," False\n",[234,445,447],{"class":159,"line":446},16,[234,448,257],{"emptyLinePlaceholder":256},[234,450,452,454,457],{"class":159,"line":451},17,[234,453,281],{"class":239},[234,455,456],{"class":266}," finished",[234,458,459],{"class":243},"(self, ok):\n",[234,461,463,466,468,471,474,477,480],{"class":159,"line":462},18,[234,464,465],{"class":243},"        level ",[234,467,321],{"class":239},[234,469,470],{"class":243}," Qgis.Info ",[234,472,473],{"class":239},"if",[234,475,476],{"class":243}," ok ",[234,478,479],{"class":239},"else",[234,481,482],{"class":243}," Qgis.Warning\n",[234,484,486,489,492,496,499,502,505,508,511,514,517,519,521,523,526,528,530,532,535],{"class":159,"line":485},19,[234,487,488],{"class":243},"        QgsMessageLog.logMessage(",[234,490,491],{"class":239},"f",[234,493,495],{"class":494},"sU2Wk","\"",[234,497,498],{"class":284},"{self",[234,500,501],{"class":243},".description()",[234,503,504],{"class":284},"}",[234,506,507],{"class":494},": ",[234,509,510],{"class":284},"{",[234,512,513],{"class":494},"'ok'",[234,515,516],{"class":239}," if",[234,518,476],{"class":243},[234,520,479],{"class":239},[234,522,381],{"class":284},[234,524,525],{"class":243},".error",[234,527,504],{"class":284},[234,529,495],{"class":494},[234,531,389],{"class":243},[234,533,534],{"class":494},"\"Chain\"",[234,536,537],{"class":243},", level)\n",[234,539,541],{"class":159,"line":540},20,[234,542,257],{"emptyLinePlaceholder":256},[234,544,546,549,551],{"class":159,"line":545},21,[234,547,548],{"class":243},"results ",[234,550,321],{"class":239},[234,552,553],{"class":243}," {}\n",[234,555,557,560,562,565,568,570,573,576,579],{"class":159,"line":556},22,[234,558,559],{"class":243},"download ",[234,561,321],{"class":239},[234,563,564],{"class":243}," Step(",[234,566,567],{"class":494},"\"download\"",[234,569,389],{"class":243},[234,571,572],{"class":239},"lambda",[234,574,575],{"class":243}," t, r: ",[234,577,578],{"class":494},"\"\u002Ftmp\u002Finput.gpkg\"",[234,580,581],{"class":243},", results)\n",[234,583,585,588,590,592,595,597,599,601,603,606,608,611,614,617,619,621],{"class":159,"line":584},23,[234,586,587],{"class":243},"validate ",[234,589,321],{"class":239},[234,591,564],{"class":243},[234,593,594],{"class":494},"\"validate\"",[234,596,389],{"class":243},[234,598,572],{"class":239},[234,600,575],{"class":243},[234,602,491],{"class":239},[234,604,605],{"class":494},"\"checked ",[234,607,510],{"class":284},[234,609,610],{"class":243},"r[",[234,612,613],{"class":494},"'download'",[234,615,616],{"class":243},"]",[234,618,504],{"class":284},[234,620,495],{"class":494},[234,622,581],{"class":243},[234,624,626,629,631,633,636,638,640,642,644,647,649,651,654,656,658,660],{"class":159,"line":625},24,[234,627,628],{"class":243},"load ",[234,630,321],{"class":239},[234,632,564],{"class":243},[234,634,635],{"class":494},"\"load\"",[234,637,389],{"class":243},[234,639,572],{"class":239},[234,641,575],{"class":243},[234,643,491],{"class":239},[234,645,646],{"class":494},"\"loaded after ",[234,648,510],{"class":284},[234,650,610],{"class":243},[234,652,653],{"class":494},"'validate'",[234,655,616],{"class":243},[234,657,504],{"class":284},[234,659,495],{"class":494},[234,661,581],{"class":243},[234,663,665],{"class":159,"line":664},25,[234,666,257],{"emptyLinePlaceholder":256},[234,668,670,673,675,678,681,683,685],{"class":159,"line":669},26,[234,671,672],{"class":243},"parent ",[234,674,321],{"class":239},[234,676,677],{"class":243}," QgsTask.fromFunction(",[234,679,680],{"class":494},"\"Sync job\"",[234,682,389],{"class":243},[234,684,572],{"class":239},[234,686,687],{"class":243}," task: results)\n",[234,689,691],{"class":159,"line":690},27,[234,692,693],{"class":243},"parent.addSubTask(download, [], QgsTask.ParentDependsOnSubTask)\n",[234,695,697],{"class":159,"line":696},28,[234,698,699],{"class":243},"parent.addSubTask(validate, [download], QgsTask.ParentDependsOnSubTask)\n",[234,701,703],{"class":159,"line":702},29,[234,704,705],{"class":243},"parent.addSubTask(load, [validate], QgsTask.ParentDependsOnSubTask)\n",[234,707,709],{"class":159,"line":708},30,[234,710,711],{"class":243},"QgsApplication.taskManager().addTask(parent)\n",[14,713,714,718,719,722,723,726,727,729,730,733,734,736,737,739],{},[715,716,717],"strong",{},"Breakdown:"," ",[18,720,721],{},"addSubTask(task, dependencies, type)"," takes the list of tasks this one must wait for. ",[18,724,725],{},"validate"," depends on ",[18,728,100],{},", and ",[18,731,732],{},"load"," on ",[18,735,725],{},", so they run in sequence even though the task manager could run them in parallel. ",[18,738,219],{}," makes the parent finish only after its subtasks — and fail if any of them fails. Results pass through a shared dictionary keyed by step name; each step reads what earlier steps wrote. Only add the parent to the task manager; subtasks are scheduled with it.",[186,741,743],{"id":742},"run-independent-steps-in-parallel","Run independent steps in parallel",[14,745,746],{},"Steps with no dependency on each other run at the same time on the task manager's thread pool. Give them the same upstream dependency and a downstream task that depends on all of them.",[14,748,749],{},[39,750,753,756,759,762,765,771,776,779,782,786,789,793,798,801,803,806,809],{"viewBox":751,"role":42,"ariaLabel":752,"xmlns":44},"0 0 760 230","Timelines comparing serial steps with independent steps run in parallel between a download and a combine step",[46,754,755],{},"Serial versus parallel subtasks",[50,757,758],{},"Serial: three steps of 4 seconds each take 12 seconds. Parallel: three independent 4-second clips run together after a 2-second download and are followed by a 2-second combine, finishing in about 8 seconds. Parallelism helps when steps are independent and the machine has free cores; steps that write to the same file must stay serial.",[54,760],{"x":56,"y":56,"width":57,"height":761,"fill":59},"230",[78,763,764],{"x":80,"y":81,"style":82,"fill":83,"textAnchor":84},"Independent steps overlap",[78,766,770],{"x":88,"y":767,"style":768,"fill":105,"textAnchor":769},"72","text-anchor:start;font-size:11.0px;font-family:sans-serif","start","serial",[54,772],{"x":89,"y":773,"width":774,"height":88,"rx":56,"fill":112,"stroke":113,"style":775},"56","160","stroke-width:1.5",[54,777],{"x":778,"y":773,"width":774,"height":88,"rx":56,"fill":112,"stroke":113,"style":775},"260",[54,780],{"x":781,"y":773,"width":774,"height":88,"rx":56,"fill":112,"stroke":113,"style":775},"420",[78,783,785],{"x":784,"y":767,"style":768,"fill":76,"textAnchor":769},"590","12 s",[78,787,788],{"x":88,"y":180,"style":768,"fill":105,"textAnchor":769},"parallel",[54,790],{"x":89,"y":791,"width":792,"height":88,"rx":56,"fill":92,"stroke":93,"style":775},"126","80",[54,794],{"x":795,"y":796,"width":774,"height":797,"rx":56,"fill":112,"stroke":113,"style":775},"180","110","18",[54,799],{"x":795,"y":800,"width":774,"height":797,"rx":56,"fill":112,"stroke":113,"style":775},"130",[54,802],{"x":795,"y":90,"width":774,"height":797,"rx":56,"fill":112,"stroke":113,"style":775},[54,804],{"x":805,"y":791,"width":792,"height":88,"rx":56,"fill":136,"stroke":137,"style":775},"340",[78,807,808],{"x":174,"y":180,"style":768,"fill":76,"textAnchor":769},"≈ 8 s",[78,810,812],{"x":89,"y":811,"style":768,"fill":105,"textAnchor":769},"200","download → three clips at once → combine",[225,814,816],{"className":227,"code":815,"language":229,"meta":230,"style":230},"results = {}\nclip_a = Step(\"clip A\", lambda t, r: \"a.gpkg\", results)\nclip_b = Step(\"clip B\", lambda t, r: \"b.gpkg\", results)\nclip_c = Step(\"clip C\", lambda t, r: \"c.gpkg\", results)\ncombine = Step(\"combine\", lambda t, r: [r[\"clip A\"], r[\"clip B\"], r[\"clip C\"]], results)\ndownload = Step(\"download\", lambda t, r: \"\u002Ftmp\u002Finput.gpkg\", results)\n\njob = QgsTask.fromFunction(\"Analysis\", lambda task: results)\njob.addSubTask(download, [], QgsTask.ParentDependsOnSubTask)\nfor clip in (clip_a, clip_b, clip_c):\n    job.addSubTask(clip, [download], QgsTask.ParentDependsOnSubTask)\njob.addSubTask(combine, [clip_a, clip_b, clip_c], QgsTask.ParentDependsOnSubTask)\nQgsApplication.taskManager().addTask(job)\n",[18,817,818,826,849,872,895,928,948,952,970,975,989,994,999],{"__ignoreMap":230},[234,819,820,822,824],{"class":159,"line":236},[234,821,548],{"class":243},[234,823,321],{"class":239},[234,825,553],{"class":243},[234,827,828,831,833,835,838,840,842,844,847],{"class":159,"line":253},[234,829,830],{"class":243},"clip_a ",[234,832,321],{"class":239},[234,834,564],{"class":243},[234,836,837],{"class":494},"\"clip A\"",[234,839,389],{"class":243},[234,841,572],{"class":239},[234,843,575],{"class":243},[234,845,846],{"class":494},"\"a.gpkg\"",[234,848,581],{"class":243},[234,850,851,854,856,858,861,863,865,867,870],{"class":159,"line":260},[234,852,853],{"class":243},"clip_b ",[234,855,321],{"class":239},[234,857,564],{"class":243},[234,859,860],{"class":494},"\"clip B\"",[234,862,389],{"class":243},[234,864,572],{"class":239},[234,866,575],{"class":243},[234,868,869],{"class":494},"\"b.gpkg\"",[234,871,581],{"class":243},[234,873,874,877,879,881,884,886,888,890,893],{"class":159,"line":278},[234,875,876],{"class":243},"clip_c ",[234,878,321],{"class":239},[234,880,564],{"class":243},[234,882,883],{"class":494},"\"clip C\"",[234,885,389],{"class":243},[234,887,572],{"class":239},[234,889,575],{"class":243},[234,891,892],{"class":494},"\"c.gpkg\"",[234,894,581],{"class":243},[234,896,897,900,902,904,907,909,911,914,916,919,921,923,925],{"class":159,"line":291},[234,898,899],{"class":243},"combine ",[234,901,321],{"class":239},[234,903,564],{"class":243},[234,905,906],{"class":494},"\"combine\"",[234,908,389],{"class":243},[234,910,572],{"class":239},[234,912,913],{"class":243}," t, r: [r[",[234,915,837],{"class":494},[234,917,918],{"class":243},"], r[",[234,920,860],{"class":494},[234,922,918],{"class":243},[234,924,883],{"class":494},[234,926,927],{"class":243},"]], results)\n",[234,929,930,932,934,936,938,940,942,944,946],{"class":159,"line":306},[234,931,559],{"class":243},[234,933,321],{"class":239},[234,935,564],{"class":243},[234,937,567],{"class":494},[234,939,389],{"class":243},[234,941,572],{"class":239},[234,943,575],{"class":243},[234,945,578],{"class":494},[234,947,581],{"class":243},[234,949,950],{"class":159,"line":327},[234,951,257],{"emptyLinePlaceholder":256},[234,953,954,957,959,961,964,966,968],{"class":159,"line":340},[234,955,956],{"class":243},"job ",[234,958,321],{"class":239},[234,960,677],{"class":243},[234,962,963],{"class":494},"\"Analysis\"",[234,965,389],{"class":243},[234,967,572],{"class":239},[234,969,687],{"class":243},[234,971,972],{"class":159,"line":345},[234,973,974],{"class":243},"job.addSubTask(download, [], QgsTask.ParentDependsOnSubTask)\n",[234,976,977,980,983,986],{"class":159,"line":356},[234,978,979],{"class":239},"for",[234,981,982],{"class":243}," clip ",[234,984,985],{"class":239},"in",[234,987,988],{"class":243}," (clip_a, clip_b, clip_c):\n",[234,990,991],{"class":159,"line":365},[234,992,993],{"class":243},"    job.addSubTask(clip, [download], QgsTask.ParentDependsOnSubTask)\n",[234,995,996],{"class":159,"line":397},[234,997,998],{"class":243},"job.addSubTask(combine, [clip_a, clip_b, clip_c], QgsTask.ParentDependsOnSubTask)\n",[234,1000,1001],{"class":159,"line":411},[234,1002,1003],{"class":243},"QgsApplication.taskManager().addTask(job)\n",[14,1005,1006,1008,1009,1011],{},[715,1007,717],{}," The three clips depend only on the download, so the task manager starts them together once it finishes. ",[18,1010,142],{}," lists all three as dependencies and waits for the last. How many actually run at once depends on the thread pool size, which follows the number of cores. Writing to separate outputs is what makes parallel steps safe; two tasks writing the same GeoPackage must be serialised with a dependency.",[186,1013,1015],{"id":1014},"depend-on-layers-not-just-tasks","Depend on layers, not just tasks",[14,1017,1018],{},"A task that reads a layer in the background would crash if the user removed that layer mid-run. Declaring layer dependencies makes the task manager cancel the task instead.",[225,1020,1022],{"className":227,"code":1021,"language":229,"meta":230,"style":230},"class ExportTask(QgsTask):\n    def __init__(self, layer, path):\n        super().__init__(f\"Export {layer.name()}\", QgsTask.CanCancel)\n        self.source = layer.dataProvider().dataSourceUri()\n        self.path = path\n        self.setDependentLayers([layer])\n\n    def run(self):\n        from qgis.core import QgsVectorLayer\n        src = QgsVectorLayer(self.source, \"copy\", \"ogr\")\n        n = src.featureCount()\n        for i, f in enumerate(src.getFeatures()):\n            if self.isCanceled():\n                return False\n            self.setProgress(100 * i \u002F max(n, 1))\n        return True\n",[18,1023,1024,1037,1046,1073,1085,1097,1104,1108,1116,1128,1154,1164,1180,1190,1197,1227],{"__ignoreMap":230},[234,1025,1026,1028,1031,1033,1035],{"class":159,"line":236},[234,1027,263],{"class":239},[234,1029,1030],{"class":266}," ExportTask",[234,1032,270],{"class":243},[234,1034,20],{"class":266},[234,1036,275],{"class":243},[234,1038,1039,1041,1043],{"class":159,"line":253},[234,1040,281],{"class":239},[234,1042,285],{"class":284},[234,1044,1045],{"class":243},"(self, layer, path):\n",[234,1047,1048,1050,1052,1054,1056,1058,1061,1063,1066,1068,1070],{"class":159,"line":260},[234,1049,294],{"class":284},[234,1051,297],{"class":243},[234,1053,300],{"class":284},[234,1055,270],{"class":243},[234,1057,491],{"class":239},[234,1059,1060],{"class":494},"\"Export ",[234,1062,510],{"class":284},[234,1064,1065],{"class":243},"layer.name()",[234,1067,504],{"class":284},[234,1069,495],{"class":494},[234,1071,1072],{"class":243},", QgsTask.CanCancel)\n",[234,1074,1075,1077,1080,1082],{"class":159,"line":278},[234,1076,309],{"class":284},[234,1078,1079],{"class":243},".source ",[234,1081,321],{"class":239},[234,1083,1084],{"class":243}," layer.dataProvider().dataSourceUri()\n",[234,1086,1087,1089,1092,1094],{"class":159,"line":291},[234,1088,309],{"class":284},[234,1090,1091],{"class":243},".path ",[234,1093,321],{"class":239},[234,1095,1096],{"class":243}," path\n",[234,1098,1099,1101],{"class":159,"line":306},[234,1100,309],{"class":284},[234,1102,1103],{"class":243},".setDependentLayers([layer])\n",[234,1105,1106],{"class":159,"line":327},[234,1107,257],{"emptyLinePlaceholder":256},[234,1109,1110,1112,1114],{"class":159,"line":340},[234,1111,281],{"class":239},[234,1113,350],{"class":266},[234,1115,353],{"class":243},[234,1117,1118,1121,1123,1125],{"class":159,"line":345},[234,1119,1120],{"class":239},"        from",[234,1122,244],{"class":243},[234,1124,247],{"class":239},[234,1126,1127],{"class":243}," QgsVectorLayer\n",[234,1129,1130,1133,1135,1138,1140,1143,1146,1148,1151],{"class":159,"line":356},[234,1131,1132],{"class":243},"        src ",[234,1134,321],{"class":239},[234,1136,1137],{"class":243}," QgsVectorLayer(",[234,1139,315],{"class":284},[234,1141,1142],{"class":243},".source, ",[234,1144,1145],{"class":494},"\"copy\"",[234,1147,389],{"class":243},[234,1149,1150],{"class":494},"\"ogr\"",[234,1152,1153],{"class":243},")\n",[234,1155,1156,1159,1161],{"class":159,"line":365},[234,1157,1158],{"class":243},"        n ",[234,1160,321],{"class":239},[234,1162,1163],{"class":243}," src.featureCount()\n",[234,1165,1166,1169,1172,1174,1177],{"class":159,"line":397},[234,1167,1168],{"class":239},"        for",[234,1170,1171],{"class":243}," i, f ",[234,1173,985],{"class":239},[234,1175,1176],{"class":284}," enumerate",[234,1178,1179],{"class":243},"(src.getFeatures()):\n",[234,1181,1182,1185,1187],{"class":159,"line":411},[234,1183,1184],{"class":239},"            if",[234,1186,381],{"class":284},[234,1188,1189],{"class":243},".isCanceled():\n",[234,1191,1192,1195],{"class":159,"line":426},[234,1193,1194],{"class":239},"                return",[234,1196,443],{"class":284},[234,1198,1199,1201,1204,1206,1209,1212,1215,1218,1221,1224],{"class":159,"line":438},[234,1200,368],{"class":284},[234,1202,1203],{"class":243},".setProgress(",[234,1205,89],{"class":284},[234,1207,1208],{"class":239}," *",[234,1210,1211],{"class":243}," i ",[234,1213,1214],{"class":239},"\u002F",[234,1216,1217],{"class":284}," max",[234,1219,1220],{"class":243},"(n, ",[234,1222,1223],{"class":284},"1",[234,1225,1226],{"class":243},"))\n",[234,1228,1229,1232],{"class":159,"line":446},[234,1230,1231],{"class":239},"        return",[234,1233,1234],{"class":284}," True\n",[14,1236,1237,718,1239,1242],{},[715,1238,717],{},[18,1240,1241],{},"setDependentLayers"," tells the task manager which layers the task needs; removing any of them cancels the task. Even so, the task opens its own copy of the source rather than iterating the project layer from a worker thread, which is the safe pattern for layer access off the main thread. The cancellation check in the loop lets the task stop promptly, and progress feeds the task bar in the status bar.",[186,1244,1246],{"id":1245},"handle-failure-and-cancellation-across-the-chain","Handle failure and cancellation across the chain",[14,1248,1249,1250,1252],{},"When a subtask fails or is cancelled, its dependants never run and the parent reports failure. The parent's ",[18,1251,154],{}," is the single place to tell the user what happened.",[14,1254,1255],{},[39,1256,1259,1262,1265,1268,1275,1278,1281,1285,1289,1294,1297,1301,1306,1309,1313,1316,1319,1322,1328,1331],{"viewBox":1257,"role":42,"ariaLabel":1258,"xmlns":44},"0 0 760 220","A failed parallel step cancelling the dependent combine step so the parent reports failure",[46,1260,1261],{},"Failure stops the chain",[50,1263,1264],{},"If clip B fails, combine depends on it and is cancelled without running. Clip A and clip C may already have finished. The parent task finishes with failure and its finished method reports which step failed, using the error recorded on that step. Partial outputs from clip A and clip C should be cleaned up or kept deliberately.",[54,1266],{"x":56,"y":56,"width":57,"height":1267,"fill":59},"220",[61,1269,1270],{},[64,1271,1273],{"id":1272,"viewBox":67,"refX":68,"refY":69,"markerWidth":70,"markerHeight":70,"orient":71},"ctFailureArrow",[73,1274],{"d":75,"fill":76},[78,1276,1277],{"x":80,"y":81,"style":82,"fill":83,"textAnchor":84},"One failed step ends the job",[54,1279],{"x":88,"y":91,"width":774,"height":1280,"rx":68,"fill":136,"stroke":137,"style":94},"60",[78,1282,118],{"x":1283,"y":1284,"style":99,"fill":141,"textAnchor":84},"104","93.51",[78,1286,1288],{"x":1283,"y":1287,"style":104,"fill":105,"textAnchor":84},"114.41","done",[54,1290],{"x":88,"y":1291,"width":774,"height":1280,"rx":68,"fill":1292,"stroke":1293,"style":94},"140","#fdf2e2","#b91c1c",[78,1295,125],{"x":1283,"y":1296,"style":99,"fill":1293,"textAnchor":84},"163.51",[78,1298,1300],{"x":1283,"y":1299,"style":104,"fill":105,"textAnchor":84},"184.41","failed",[54,1302],{"x":1303,"y":89,"width":774,"height":1304,"rx":68,"fill":1292,"stroke":1305,"style":94},"300","64","#b45309",[78,1307,142],{"x":80,"y":1308,"style":99,"fill":1305,"textAnchor":84},"125.51",[78,1310,1312],{"x":80,"y":1311,"style":104,"fill":105,"textAnchor":84},"146.41","cancelled",[54,1314],{"x":1315,"y":89,"width":774,"height":1304,"rx":68,"fill":1292,"stroke":1293,"style":94},"576",[78,1317,106],{"x":1318,"y":1308,"style":99,"fill":1293,"textAnchor":84},"656",[78,1320,1321],{"x":1318,"y":1311,"style":104,"fill":105,"textAnchor":84},"reports failure",[159,1323],{"x1":1324,"y1":89,"x2":1325,"y2":1326,"stroke":76,"style":1327},"184","298","122","stroke-width:1.8;marker-end:url(#ctFailureArrow)",[159,1329],{"x1":1324,"y1":1330,"x2":1325,"y2":180,"stroke":76,"style":1327},"170",[159,1332],{"x1":1333,"y1":1334,"x2":1335,"y2":1334,"stroke":76,"style":1327},"460","132","574",[225,1337,1339],{"className":227,"code":1338,"language":229,"meta":230,"style":230},"class Job(QgsTask):\n    def __init__(self, steps):\n        super().__init__(\"Analysis job\", QgsTask.CanCancel)\n        self.steps = steps\n\n    def run(self):\n        return True                          # subtasks do the work\n\n    def finished(self, ok):\n        from qgis.utils import iface\n        if ok:\n            iface.messageBar().pushSuccess(\"Analysis\", \"All steps finished\")\n            return\n        failed = [s.description() for s in self.steps if s.error]\n        reason = \", \".join(failed) or \"cancelled\"\n        iface.messageBar().pushWarning(\"Analysis\", f\"Stopped: {reason}\")\n",[18,1340,1341,1354,1363,1378,1390,1394,1402,1413,1417,1425,1437,1445,1459,1464,1490,1509],{"__ignoreMap":230},[234,1342,1343,1345,1348,1350,1352],{"class":159,"line":236},[234,1344,263],{"class":239},[234,1346,1347],{"class":266}," Job",[234,1349,270],{"class":243},[234,1351,20],{"class":266},[234,1353,275],{"class":243},[234,1355,1356,1358,1360],{"class":159,"line":253},[234,1357,281],{"class":239},[234,1359,285],{"class":284},[234,1361,1362],{"class":243},"(self, steps):\n",[234,1364,1365,1367,1369,1371,1373,1376],{"class":159,"line":260},[234,1366,294],{"class":284},[234,1368,297],{"class":243},[234,1370,300],{"class":284},[234,1372,270],{"class":243},[234,1374,1375],{"class":494},"\"Analysis job\"",[234,1377,1072],{"class":243},[234,1379,1380,1382,1385,1387],{"class":159,"line":278},[234,1381,309],{"class":284},[234,1383,1384],{"class":243},".steps ",[234,1386,321],{"class":239},[234,1388,1389],{"class":243}," steps\n",[234,1391,1392],{"class":159,"line":291},[234,1393,257],{"emptyLinePlaceholder":256},[234,1395,1396,1398,1400],{"class":159,"line":306},[234,1397,281],{"class":239},[234,1399,350],{"class":266},[234,1401,353],{"class":243},[234,1403,1404,1406,1409],{"class":159,"line":327},[234,1405,1231],{"class":239},[234,1407,1408],{"class":284}," True",[234,1410,1412],{"class":1411},"sjoCn","                          # subtasks do the work\n",[234,1414,1415],{"class":159,"line":340},[234,1416,257],{"emptyLinePlaceholder":256},[234,1418,1419,1421,1423],{"class":159,"line":345},[234,1420,281],{"class":239},[234,1422,456],{"class":266},[234,1424,459],{"class":243},[234,1426,1427,1429,1432,1434],{"class":159,"line":356},[234,1428,1120],{"class":239},[234,1430,1431],{"class":243}," qgis.utils ",[234,1433,247],{"class":239},[234,1435,1436],{"class":243}," iface\n",[234,1438,1439,1442],{"class":159,"line":365},[234,1440,1441],{"class":239},"        if",[234,1443,1444],{"class":243}," ok:\n",[234,1446,1447,1450,1452,1454,1457],{"class":159,"line":397},[234,1448,1449],{"class":243},"            iface.messageBar().pushSuccess(",[234,1451,963],{"class":494},[234,1453,389],{"class":243},[234,1455,1456],{"class":494},"\"All steps finished\"",[234,1458,1153],{"class":243},[234,1460,1461],{"class":159,"line":411},[234,1462,1463],{"class":239},"            return\n",[234,1465,1466,1469,1471,1474,1476,1479,1481,1483,1485,1487],{"class":159,"line":426},[234,1467,1468],{"class":243},"        failed ",[234,1470,321],{"class":239},[234,1472,1473],{"class":243}," [s.description() ",[234,1475,979],{"class":239},[234,1477,1478],{"class":243}," s ",[234,1480,985],{"class":239},[234,1482,381],{"class":284},[234,1484,1384],{"class":243},[234,1486,473],{"class":239},[234,1488,1489],{"class":243}," s.error]\n",[234,1491,1492,1495,1497,1500,1503,1506],{"class":159,"line":438},[234,1493,1494],{"class":243},"        reason ",[234,1496,321],{"class":239},[234,1498,1499],{"class":494}," \", \"",[234,1501,1502],{"class":243},".join(failed) ",[234,1504,1505],{"class":239},"or",[234,1507,1508],{"class":494}," \"cancelled\"\n",[234,1510,1511,1514,1516,1518,1520,1523,1525,1528,1530,1532],{"class":159,"line":446},[234,1512,1513],{"class":243},"        iface.messageBar().pushWarning(",[234,1515,963],{"class":494},[234,1517,389],{"class":243},[234,1519,491],{"class":239},[234,1521,1522],{"class":494},"\"Stopped: ",[234,1524,510],{"class":284},[234,1526,1527],{"class":243},"reason",[234,1529,504],{"class":284},[234,1531,495],{"class":494},[234,1533,1153],{"class":243},[14,1535,1536,1538,1539,1541,1542,1544,1545,1548],{},[715,1537,717],{}," The parent's ",[18,1540,205],{}," can do nothing when the subtasks carry the work; its ",[18,1543,154],{}," runs on the main thread, so it can safely touch the interface. Collecting the error recorded on each step names the failure precisely. Cancelling the parent — from the task bar or with ",[18,1546,1547],{},"job.cancel()"," — cancels all its subtasks too, so a single cancel button covers the whole chain. Decide whether partial outputs should be deleted or kept for a retry.",[186,1550,1552],{"id":1551},"keep-references-alive","Keep references alive",[14,1554,1555],{},"Tasks created in a function and not stored anywhere may be garbage collected by Python before they run, which crashes QGIS or silently drops the work.",[225,1557,1559],{"className":227,"code":1558,"language":229,"meta":230,"style":230},"class Plugin:\n    def start_job(self):\n        self.job = Job([])                   # keep a reference on the plugin\n        QgsApplication.taskManager().addTask(self.job)\n",[18,1560,1561,1570,1579,1594],{"__ignoreMap":230},[234,1562,1563,1565,1568],{"class":159,"line":236},[234,1564,263],{"class":239},[234,1566,1567],{"class":266}," Plugin",[234,1569,362],{"class":243},[234,1571,1572,1574,1577],{"class":159,"line":253},[234,1573,281],{"class":239},[234,1575,1576],{"class":266}," start_job",[234,1578,353],{"class":243},[234,1580,1581,1583,1586,1588,1591],{"class":159,"line":260},[234,1582,309],{"class":284},[234,1584,1585],{"class":243},".job ",[234,1587,321],{"class":239},[234,1589,1590],{"class":243}," Job([])                   ",[234,1592,1593],{"class":1411},"# keep a reference on the plugin\n",[234,1595,1596,1599,1601],{"class":159,"line":278},[234,1597,1598],{"class":243},"        QgsApplication.taskManager().addTask(",[234,1600,315],{"class":284},[234,1602,1603],{"class":243},".job)\n",[14,1605,1606,1608,1609,1612],{},[715,1607,717],{}," Storing the parent task on the plugin instance keeps it and its subtasks alive until they finish. Tasks created with ",[18,1610,1611],{},"QgsTask.fromFunction"," need the same care. This is the single most common cause of \"my task never ran\" reports.",[186,1614,1616],{"id":1615},"add-the-result-to-the-map-when-the-chain-ends","Add the result to the map when the chain ends",[14,1618,1619,1620,1622],{},"Background steps must never add layers to the project themselves — the project belongs to the main thread. The parent's ",[18,1621,154],{}," method, which runs on the main thread, is where the output of the last step becomes a map layer.",[225,1624,1626],{"className":227,"code":1625,"language":229,"meta":230,"style":230},"from qgis.core import QgsProject, QgsVectorLayer\n\nclass LoadingJob(Job):\n    def __init__(self, steps, results):\n        super().__init__(steps)\n        self.results = results\n\n    def finished(self, ok):\n        super().finished(ok)\n        if not ok:\n            return\n        for path in self.results.get(\"combine\", []):\n            layer = QgsVectorLayer(path, path.rsplit(\"\u002F\", 1)[-1], \"ogr\")\n            if layer.isValid():\n                QgsProject.instance().addMapLayer(layer)\n",[18,1627,1628,1639,1643,1657,1666,1677,1688,1692,1700,1707,1715,1719,1738,1770,1777],{"__ignoreMap":230},[234,1629,1630,1632,1634,1636],{"class":159,"line":236},[234,1631,240],{"class":239},[234,1633,244],{"class":243},[234,1635,247],{"class":239},[234,1637,1638],{"class":243}," QgsProject, QgsVectorLayer\n",[234,1640,1641],{"class":159,"line":253},[234,1642,257],{"emptyLinePlaceholder":256},[234,1644,1645,1647,1650,1652,1655],{"class":159,"line":260},[234,1646,263],{"class":239},[234,1648,1649],{"class":266}," LoadingJob",[234,1651,270],{"class":243},[234,1653,1654],{"class":266},"Job",[234,1656,275],{"class":243},[234,1658,1659,1661,1663],{"class":159,"line":278},[234,1660,281],{"class":239},[234,1662,285],{"class":284},[234,1664,1665],{"class":243},"(self, steps, results):\n",[234,1667,1668,1670,1672,1674],{"class":159,"line":291},[234,1669,294],{"class":284},[234,1671,297],{"class":243},[234,1673,300],{"class":284},[234,1675,1676],{"class":243},"(steps)\n",[234,1678,1679,1681,1683,1685],{"class":159,"line":306},[234,1680,309],{"class":284},[234,1682,318],{"class":243},[234,1684,321],{"class":239},[234,1686,1687],{"class":243}," results\n",[234,1689,1690],{"class":159,"line":327},[234,1691,257],{"emptyLinePlaceholder":256},[234,1693,1694,1696,1698],{"class":159,"line":340},[234,1695,281],{"class":239},[234,1697,456],{"class":266},[234,1699,459],{"class":243},[234,1701,1702,1704],{"class":159,"line":345},[234,1703,294],{"class":284},[234,1705,1706],{"class":243},"().finished(ok)\n",[234,1708,1709,1711,1713],{"class":159,"line":356},[234,1710,1441],{"class":239},[234,1712,403],{"class":239},[234,1714,1444],{"class":243},[234,1716,1717],{"class":159,"line":365},[234,1718,1463],{"class":239},[234,1720,1721,1723,1726,1728,1730,1733,1735],{"class":159,"line":397},[234,1722,1168],{"class":239},[234,1724,1725],{"class":243}," path ",[234,1727,985],{"class":239},[234,1729,381],{"class":284},[234,1731,1732],{"class":243},".results.get(",[234,1734,906],{"class":494},[234,1736,1737],{"class":243},", []):\n",[234,1739,1740,1743,1745,1748,1751,1753,1755,1758,1761,1763,1766,1768],{"class":159,"line":411},[234,1741,1742],{"class":243},"            layer ",[234,1744,321],{"class":239},[234,1746,1747],{"class":243}," QgsVectorLayer(path, path.rsplit(",[234,1749,1750],{"class":494},"\"\u002F\"",[234,1752,389],{"class":243},[234,1754,1223],{"class":284},[234,1756,1757],{"class":243},")[",[234,1759,1760],{"class":239},"-",[234,1762,1223],{"class":284},[234,1764,1765],{"class":243},"], ",[234,1767,1150],{"class":494},[234,1769,1153],{"class":243},[234,1771,1772,1774],{"class":159,"line":426},[234,1773,1184],{"class":239},[234,1775,1776],{"class":243}," layer.isValid():\n",[234,1778,1779],{"class":159,"line":438},[234,1780,1781],{"class":243},"                QgsProject.instance().addMapLayer(layer)\n",[14,1783,1784,1786,1787,1789,1790,1793,1794,1796,1797,1799],{},[715,1785,717],{}," The worker steps only write files and record their paths in the shared results. Once every step has succeeded, ",[18,1788,154],{}," opens those files as layers and adds them to the project, safely on the main thread. Checking ",[18,1791,1792],{},"isValid"," guards against a step that reported success but wrote an empty or unreadable file. The same pattern applies to updating a dock widget, refreshing the canvas or writing project entries: do it in ",[18,1795,154],{},", never in ",[18,1798,205],{},".",[186,1801,1803],{"id":1802},"wait-for-a-chain-in-a-standalone-script","Wait for a chain in a standalone script",[14,1805,1806],{},"In a script run outside the QGIS window, nothing processes events while the tasks run, so the script must wait for the task manager explicitly.",[225,1808,1810],{"className":227,"code":1809,"language":229,"meta":230,"style":230},"from qgis.core import QgsApplication\n\nmanager = QgsApplication.taskManager()\nmanager.addTask(job)\nwhile manager.countActiveTasks() > 0:\n    QgsApplication.processEvents()\nprint(\"job finished with status\", job.status())\n",[18,1811,1812,1823,1827,1837,1842,1858,1863],{"__ignoreMap":230},[234,1813,1814,1816,1818,1820],{"class":159,"line":236},[234,1815,240],{"class":239},[234,1817,244],{"class":243},[234,1819,247],{"class":239},[234,1821,1822],{"class":243}," QgsApplication\n",[234,1824,1825],{"class":159,"line":253},[234,1826,257],{"emptyLinePlaceholder":256},[234,1828,1829,1832,1834],{"class":159,"line":260},[234,1830,1831],{"class":243},"manager ",[234,1833,321],{"class":239},[234,1835,1836],{"class":243}," QgsApplication.taskManager()\n",[234,1838,1839],{"class":159,"line":278},[234,1840,1841],{"class":243},"manager.addTask(job)\n",[234,1843,1844,1847,1850,1853,1856],{"class":159,"line":291},[234,1845,1846],{"class":239},"while",[234,1848,1849],{"class":243}," manager.countActiveTasks() ",[234,1851,1852],{"class":239},">",[234,1854,1855],{"class":284}," 0",[234,1857,362],{"class":243},[234,1859,1860],{"class":159,"line":306},[234,1861,1862],{"class":243},"    QgsApplication.processEvents()\n",[234,1864,1865,1868,1870,1873],{"class":159,"line":327},[234,1866,1867],{"class":284},"print",[234,1869,270],{"class":243},[234,1871,1872],{"class":494},"\"job finished with status\"",[234,1874,1875],{"class":243},", job.status())\n",[14,1877,1878,718,1880,1883,1884,1886,1887,1891],{},[715,1879,717],{},[18,1881,1882],{},"countActiveTasks"," drops to zero once the parent and all its subtasks have completed, been cancelled or failed. Processing events in the loop lets ",[18,1885,154],{}," handlers run, since they are delivered on the main thread. In ",[26,1888,1890],{"href":1889},"\u002Fpyqgis-fundamentals-environment-setup\u002Fheadless-qgis-and-server-automation\u002F","headless scripts"," that only need the work done, calling each step's logic directly in order is often simpler than tasks at all; chaining tasks pays off inside the QGIS interface, where responsiveness matters.",[186,1893,1895],{"id":1894},"qgis-version-compatibility","QGIS version compatibility",[14,1897,1898,1901,1902,1904,1905,1907],{},[18,1899,1900],{},"addSubTask",", the ",[18,1903,219],{}," flag, ",[18,1906,1241],{}," and the task manager's dependency handling are available on QGIS 3.34 LTR, 3.40 LTR and QGIS 4. In QGIS 4, enum names may also appear in scoped form; the classic names remain accepted.",[186,1909,1911],{"id":1910},"troubleshooting","Troubleshooting",[191,1913,1914,1920,1928,1934],{},[194,1915,1916,1919],{},[715,1917,1918],{},"Tasks never start."," The task object was garbage collected; keep a reference.",[194,1921,1922,1925,1926,1799],{},[715,1923,1924],{},"Steps run out of order."," A dependency is missing from ",[18,1927,1900],{},[194,1929,1930,1933],{},[715,1931,1932],{},"QGIS crashes during a task."," A project layer was read from the worker thread; open a copy from its source instead.",[194,1935,1936,1939,1940,1799],{},[715,1937,1938],{},"The parent reports success despite a failing step."," The subtask was added without ",[18,1941,219],{},[186,1943,1945],{"id":1944},"conclusion","Conclusion",[14,1947,1948,1949,1951],{},"Model multi-step jobs as a parent task with subtasks, declare order with dependency lists, let independent steps run in parallel, pass results through a shared structure, declare layer dependencies, report outcomes in the parent's ",[18,1950,154],{},", and keep references to tasks until they complete.",[186,1953,1955],{"id":1954},"frequently-asked-questions","Frequently Asked Questions",[14,1957,1958,1961],{},[715,1959,1960],{},"Can subtasks have their own subtasks?","\nYes; nesting works, though a flat list with dependencies is easier to follow.",[14,1963,1964,1967],{},[715,1965,1966],{},"How do I show overall progress?","\nThe parent's progress is derived from its subtasks in the task bar; you can also set it yourself.",[14,1969,1970,1973],{},[715,1971,1972],{},"Can a task depend on a task that is not its sibling?","\nDependencies are expressed between subtasks of the same parent, or between top-level tasks via the task manager.",[14,1975,1976,1979,1980,1984],{},[715,1977,1978],{},"Should I use Processing instead?","\nFor chains of Processing algorithms, a ",[26,1981,1983],{"href":1982},"\u002Fspatial-data-processing-automation\u002Fchaining-processing-algorithms\u002F","model or chained processing.run"," running in a task is often simpler.",[186,1986,1988],{"id":1987},"related","Related",[191,1990,1991,1996,2001,2007,2013],{},[194,1992,1993,1995],{},[26,1994,29],{"href":28}," — the guide this recipe belongs to",[194,1997,1998],{},[26,1999,2000],{"href":33},"Run a Background Task with QgsTask",[194,2002,2003],{},[26,2004,2006],{"href":2005},"\u002Fqgis-plugin-development\u002Fbackground-tasks-and-plugin-performance\u002Fshow-plugin-progress-and-cancellation-pyqgis\u002F","Show Plugin Progress and Cancellation",[194,2008,2009],{},[26,2010,2012],{"href":2011},"\u002Fqgis-plugin-development\u002Fbackground-tasks-and-plugin-performance\u002Fspeed-up-feature-iteration-with-qgsfeaturerequest-pyqgis\u002F","Speed Up Feature Iteration with QgsFeatureRequest",[194,2014,2015],{},[26,2016,2018],{"href":2017},"\u002Fqgis-plugin-development\u002Fbackground-tasks-and-plugin-performance\u002Fprofile-slow-pyqgis-code\u002F","Profile Slow PyQGIS Code",[2020,2021,2022],"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 .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}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}",{"title":230,"searchDepth":253,"depth":253,"links":2024},[2025,2026,2027,2028,2029,2030,2031,2032,2033,2034,2035,2036,2037],{"id":188,"depth":253,"text":189},{"id":212,"depth":253,"text":213},{"id":742,"depth":253,"text":743},{"id":1014,"depth":253,"text":1015},{"id":1245,"depth":253,"text":1246},{"id":1551,"depth":253,"text":1552},{"id":1615,"depth":253,"text":1616},{"id":1802,"depth":253,"text":1803},{"id":1894,"depth":253,"text":1895},{"id":1910,"depth":253,"text":1911},{"id":1944,"depth":253,"text":1945},{"id":1954,"depth":253,"text":1955},{"id":1987,"depth":253,"text":1988},"Run multi-step background work in order with QgsTask — subtasks that run in parallel or serially, dependencies between tasks, layer dependencies, passing results from one step to the next, and handling cancellation and failure across a chain.","md",{"slug":2041,"type":2042,"breadcrumb":2043,"datePublished":2044,"dateModified":2044},"chain-dependent-qgstasks-pyqgis","article","Chain Dependent QgsTasks","2026-10-02","\u002Fqgis-plugin-development\u002Fbackground-tasks-and-plugin-performance\u002Fchain-dependent-qgstasks-pyqgis",{"title":5,"description":2038},"qgis-plugin-development\u002Fbackground-tasks-and-plugin-performance\u002Fchain-dependent-qgstasks-pyqgis\u002Findex","NnoNA0ZnW2plvSYYiPW6fLJCAXL8rwW6RAyJ2HErUwM",1790966256927]