new blogpost

This commit is contained in:
Will McGugan
2023-02-11 17:08:21 +00:00
parent a37e7fcb2b
commit f5723f933e
3 changed files with 29 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 KiB

View File

@@ -0,0 +1,26 @@
---
draft: true
date: 2023-02-11
categories:
- DevLog
authors:
- willmcgugan
---
# The Heisenbug lurking in your async code
I'm taking a brief break from blogging about [Textual](https://github.com/Textualize/textual) to bring you this brief PSA for Python developers who work with async code. I wanted to expand a little on this [tweet](https://twitter.com/Mtrl_Scientist/status/1624439847174676480).
<!-- more -->
If you have ever used `asyncio.create_task` you may have created a bug for yourself that is challenging (read *almost impossible*) to reproduce. If it occurs, your code will likely fail in unpredictable ways.
The root cause of this [Heisenbug](Heisenbug) is that if you don't hold a reference to the task object returned by `create_task` then the task may simply disappear, without warning, when Python runs garbage collection. This behavior is [well documented](https://docs.python.org/3/library/asyncio-task.html#asyncio.create_task), as you can see from this excerpt:
![create task](../images/async-create-task.jpeg)
But who reads all the docs? And who has perfect recall if they do? A search on GitHub indicates that there are a [lot of projects](https://github.com/search?q=%22asyncio.create_task%28%22&type=code) where this bug is waiting just for the right moment to ruin somebody's day.
I suspect the reason this mistake is so common is that tasks are a lot like threads (conceptually at least). With threads you can just launch them and forget. Unless you mark them as "daemon" threads they will exist for the lifetime of your app. Not so with Tasks.
The solution, as recommended in the docs, is to keep a reference to the task for as long as you need it to live. You could also use [TaskGroups](https://docs.python.org/3/library/asyncio-task.html#task-groups) which will keep references to your tasks. As long as all the tasks you spin up are in TaskGroups, you should be fine.

View File

@@ -99,7 +99,10 @@ def take_svg_screenshot(
async def auto_pilot(pilot: Pilot) -> None:
app = pilot.app
await pilot.press(*press)
await pilot.wait_for_scheduled_animations()
await pilot.pause()
svg = app.export_screenshot(title=title)
app.exit(svg)
svg = app.run(