Description
In the following ExampleApp, I would expect that when the app is quit, the thread worker would terminate too. It does not, and instead the process runs forever.
When I saw that the thread worker does not terminate, my initial thought was perhaps textual failed to set the thread as a daemon, and that this would be an easy fix.
Here's the example code. It continues running even after quitting the app with ctrl-C.
import time
from textual import work
from textual.app import App
from textual.app import ComposeResult
from textual.messages import Message
from textual.widget import Widget
from textual.widgets import Log
class ExampleWidget(Widget):
class Changed(Message):
def __init__(self, name: str):
super().__init__()
self.name = name
def on_mount(self):
self.start_worker()
@work(thread=True)
def start_worker(self) -> None:
while True:
time.sleep(1)
self.post_message(self.Changed("And again..."))
class ExampleApp(App):
def compose(self) -> ComposeResult:
yield Log()
yield ExampleWidget()
if __name__ == "__main__":
app = ExampleApp()
app.run()
Surprisingly (to me) if I try to minimize the example further, then it starts working as intended and the bug goes away. In the following code, the app successfully quits on ctrl-C. The difference is that the thread worker is in the App rather than in a widget.
import time
from textual import work
from textual.app import App
from textual.app import ComposeResult
from textual.widgets import Log
class ExampleApp(App):
def compose(self) -> ComposeResult:
yield Log()
def on_mount(self):
self.start_worker()
@work(thread=True)
def start_worker(self):
log = self.query_one(Log)
while True:
log.write_line("And again...")
time.sleep(1)
if __name__ == "__main__":
app = ExampleApp()
app.run()
I did notice that in #2593 @davep notes that long-running thread workers are a bad idea. I haven't looked into alternatives yet. If you have a recommendation or rationale, do let me know.