Repository: beeware/toga

Issue:
Change in label text doesn't trigger rehint and relayout
**Describe the bug**
Using `yield` in a function that gets triggered from an buttons `on_press` event is not responding on Windows.

**To Reproduce**
Steps to reproduce the behavior:
1. Click Button
2. See No Change

**Expected behavior**
The label should show "RUN1" and after 5s change to "RUN2"

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Environment:**
 - Operating System: Linux
 - Python version: 3.9
 - Software versions:
   - Briefcase: 0.3.3 
   - Toga: 0.3.0.dev25
**Additional context**
The Installer is build with the following file:
```

# This workflow will install Python dependencies, run tests and lint with a single version of Python
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions

name: Windows

on:
  push:
    branches: [ master, workflow-test, app-path, windows-yield ]
  pull_request:
    branches: [ master ]

jobs:
  build:

    runs-on: windows-latest

    steps:
    - uses: actions/checkout@v2
    - name: Set up Python 3.8
      uses: actions/setup-python@v2
      with:
        python-version: 3.8
    - name: Install dependencies
      run: |
        #apt-get update
        #sudo apt-get install git python3-dev python3-pip libgirepository1.0-dev libcairo2-dev libpango1.0-dev libwebkitgtk-3.0-0 gir1.2-webkit-3.0
    - name: Beeware dependencies
      run: |
        # stop the build if there are Python syntax errors or undefined names
        python -m pip install beeware
        python -m pip install dhooks
    - name: Run tests with pytest
      run: |
        cd src/
        python -m unittest test_app.Test_Profile_Manager.test_swap_xml -v
        python -m unittest test_app.Test_Profile_Manager.test_delete_profile -v
        python -m unittest test_app.Test_Profile_Manager.test_create_profile -v
        python -m unittest test_app.Test_Profile_Manager.test_load_profile -v
        python -m unittest test_app.Test_Profile_Manager.test_get_ethnic -v
        python -m unittest test_app.Test_Profile_Manager.test_switching_profiles_with_invalid_path -v
        python -m unittest test_app.Test_Profile_Manager.test_migrate_function -v
        python -m unittest test_app.Test_XML_Parser -v
        python -m unittest test_app.Test_RTF_Parser -v
        python -m unittest test_app.Test_Config_Manager -v
        python -m unittest test_app.Test_Xml_Writing -v
        python -m unittest test_mapper.Test_Mapper_Generate_Mapping -v
        python -m unittest test_mapper.Test_Mapper_Preserve_Mapping -v
        python -m unittest test_mapper.Test_Mapper_Overwrite_Mapping -v
    - name: Build
      run: |
        briefcase create
        briefcase build
        briefcase package
    - name: Save Installer
      uses: actions/upload-artifact@v1
      with:
        name: NewGAN-Manager-Installer-Win
        path: windows\NewGAN Manager-1.1.0.msi
```

The `app.py`
```
"""
Yield Test
"""
import toga
import time


class NewGANManager(toga.App):

    def startup(self):
        self.main_window = toga.MainWindow(self.name)
        self.output = toga.Label("")
        self.btn = toga.Button(label="Run", on_press=self.calc)
        self.main_box = toga.Box()
        self.main_box.add(self.output)
        self.main_box.add(self.btn)
        self.main_window.content = self.main_box
        self.main_window.show()
    def calc(self, widget):
        self.output.text = "RUN1"
        yield 0.1
        time.sleep(3)
        self.output.text = "RUN2"
        yield 0.1

def main():
    return NewGANManager('Toga Demo', 'org.beeware.toga-demo')
```

more info here: https://github.com/Maradonna90/NewGAN-Manager/tree/windows-yield


Discussion:
Thanks for the report. There's definitely a bug here, but I don't think it's where you think it is.

From what I can work out, the issue actually with layout. The label initially has no content, so the layout algorithm assigns 0 width to the label; when the label is updated, the change in content doesn't cause the label to re-evaluate it's size (or, at least, doesn't alter the layout to allow for the new size).

You should be able to see exactly the same bug *without* the use of yield; if the button handler only changes the label text, you'll get the same effect.

As an immediate workaround; if you add `style=Pack(width=100)` to the output (or you give the label some initial text), you can see the label update (or, at least, it does in my testing). 

What is weird is that there should be a rehint performed the label text is updated. Some debugging is required to establish why that rehint isn't picking up the new width of the label after the content change.
It doesnt work on my end. Neither the `style=Pack(width=100)` nor with initial text.

P.S: Im just testing the built app, not in a dev environment
btw: I just stumbled across an example app for handlers https://github.com/beeware/toga/blob/master/examples/handlers/handlers/app.py

If there is a bug we should also see it there. I will reassure its an actual bug on windows with this example. In dev and in run. And I will as well build an installer and run it on a windows machine. I added some tests in my PR #1182 , those are not failing on any platform. So actually it can't be the handlers. Probably you were right all along and if there is an issue it has to do with relayout.
I did a fresh install on windows 10. create a new briefacse project and used the handlers example app from toga: https://github.com/beeware/toga/blob/master/examples/handlers/handlers/app.py

the generator and the async buttons are not working. I even initiated all labels with a empty string. Same behaviour. 

After that I edited the very first line of every function to be a print statement e.g
```
def do_generator(self, widget, **kwargs):
    print("do_generator")
```

NONE of the async function are executing this print function.

/EDIT: I think it should be in the proactor class.
I dont fully understand the idea of it but for my understanding if I do an async call ticks should be enqueued and eventually ticked. But after initial setup the proactor functions a never executed again.
Confirming that I'm also seeing this problem as well. 

The good news is that it appears to be a recent breakage. The `handlers` demo works on Windows with toga-0.3.0.dev24 (released Oct 17); but dev25 is broken. It looks like the problem lies with the patch for #1109 that was introduced to address a memory leak on Windows applications.
Ok so I narrowed the issue down even more. It is very clear that the `Dispose` function frees the memory but also breaks the loop. 

https://github.com/saroad2/toga/blob/77c8d8d962b75e3536488c9d6316f99ff634522d/src/winforms/toga_winforms/libs/proactor.py#L117-L119

```
if self.task:
    self.task.Dispose()
    del self.task
```
If we remove the Dispose line we have the memory leak back. This is most likely caused by the Timer that references the Task object it was attached to. 
```
    def enqueue_tick(self):
        # Queue a call to tick in 5ms.
        self.task = Action[Task](self.tick)
        Task.Delay(5).ContinueWith(self.task)
```

Thus the GC doesn't collect it. Probably thats also why the `del self.task` statement isn't doing anything actually.

I don't really have an idea how to solve this issue, maybe the whole construction of the proactor has to be reevaluated.

@saroad2 maybe you have an idea?
Hey @Maradonna90 ,
Actually, I have absolutly no idea, but I will try to give it a look over the weekend :)

Ping me again if for some reason I forget .
@saroad2 ping! 
Thanks for the ping!

I gave it a look for about an hour now, and I have no lead. My best suggestion is to open a ticket at [pythonnet](https://github.com/pythonnet/pythonnet) and consult with them about the issue. It seems like the problem is somewhere in the `ApplicationContext` class, but I'm really not sure.

Sorry I couldn't be more helpful.
So apparently this has nothing to do with pythonnet. 
Another bump: I found this answer on stackoverflow: https://stackoverflow.com/questions/45753171/python-net-multithreading-and-the-windows-event-loop
Looks like its exactly what we want?
@Maradonna90 Hrm... maybe. There's a lot of similarity with what we're doing right now - posting Actions into the main loop. However, I'm unclear if the piece with the getting the CurrentDispatcher will work around the problems that exist with starting the asyncio event loop in a separate thread. It's definitely worth an experiment.

As for "this has nothing to do with Python.net": you submitted a bug report that referenced some random, incomplete code from a different project; so they closed the ticket as being unrelated to Python.net. From their perspective, that was the 100% right call. That doesn't mean there isn't a problem with Python.net - it means you didn't ask a question that gave them any ability to respond. It's up to us to narrow down a *specific* use case that highlights a problem with Python.net's object disposal policy. 

Resolving patch:
diff --git a/src/winforms/toga_winforms/libs/proactor.py b/src/winforms/toga_winforms/libs/proactor.py
--- a/src/winforms/toga_winforms/libs/proactor.py
+++ b/src/winforms/toga_winforms/libs/proactor.py
@@ -114,9 +114,6 @@ def tick(self, *args, **kwargs):
         # message, it will be caught by the MessageFilter we installed on the
         # Application thread.
 
-        if self.task:
-            self.task.Dispose()
-            del self.task
         # The message is sent with:
         # * HWND 0xfff (all windows),
         # * MSG self.msg_id (a message ID in the WM_USER range)
@@ -130,8 +127,6 @@ def tick(self, *args, **kwargs):
         if self.app_context.MainForm:
             action = Action(self.run_once_recurring)
             self.app_context.MainForm.Invoke(action)
-            action.Dispose()
-            del action
 
     def run_once_recurring(self):
         """


Test patch: