Context managers solve the application use case, but they don’t solve the interactive use case.
All the potential cleanup triggers in interactive use don’t actually work:
- context managers:
withstatements can only apply to a single interactive command, they can’t span multiple commands contextlib.ExitStack: still needs awithstatement or some other callback to trigger cleanup__del__: the__main__module globals are only cleared after threads are joined at shutdownatexit: these hooks also run after threads are joined at shutdown
The last two can be made to work, but only if you mark the background thread as a daemon thread so it gets ignored by the “wait for all non-daemon threads to terminate” step at shutdown, and then set up an appropriate atexit hook to trigger the lazy cleanup.
This means the current two simplest ways to implement background threads for synchronous applications are to:
- Just make them regular threads, with only deterministic cleanup supported. These APIs hang on shutdown if you attempt to use them interactively.
- Make the background threads daemon threads, without arranging to clean them up before shutdown (in the absence of deterministic cleanup). These APIs are likely to throw exceptions on shutdown if you attempt to use them interactively.
I do think public thread-safe APIs to schedule tasks and run arbitrary callables in the background thread’s event loop would be worthwhile additions (my actual implementation has them), but the sample code in the post was already complicated enough without them (one subtle point with such injections is that it’s OK for them to be outside the termination task group, since the loop shutdown will terminate everything else after the main task gets terminated).
(Given the impact on the threading API and the shutdown process, I think this idea would need a PEP to be actually implemented, but I wanted to get feedback on it before investing that kind of time into it)