|
| 1 | +# Win32 and Asyncio Dual-Thread Collaboration Design |
| 2 | + |
| 3 | +## Architecture Diagram |
| 4 | + |
| 5 | +```mermaid |
| 6 | +graph TB |
| 7 | + subgraph "UI Thread (Win32)" |
| 8 | + A[Win32 Message Loop] --> B[Handle GUI Events] |
| 9 | + B --> C[Execute asyncio Tasks] |
| 10 | + C --> D[Release Semaphore] |
| 11 | + |
| 12 | + M[TRIO_MSG/ASYNCIO_MSG Handler] --> N[Process Task Queue] |
| 13 | + N --> O[Run asyncio.run_once] |
| 14 | + O --> P[Release Semaphore] |
| 15 | + end |
| 16 | + |
| 17 | + subgraph "Backend Thread (Asyncio)" |
| 18 | + E[Wait for Semaphore] --> F[Call epoll.poll] |
| 19 | + F --> G[Detect Events] |
| 20 | + G --> H[Request UI Processing] |
| 21 | + H --> E |
| 22 | + end |
| 23 | + |
| 24 | + subgraph "Coordination Mechanisms" |
| 25 | + I[Semaphore/Lock] |
| 26 | + J[Asyncio Ready Task Queue] |
| 27 | + K[Wake Pipe] |
| 28 | + end |
| 29 | + |
| 30 | + D --> I |
| 31 | + I --> E |
| 32 | + P --> I |
| 33 | + C --- J --- H |
| 34 | + |
| 35 | + subgraph "Timer Events" |
| 36 | + L[asyncio.call_later] --> Q[wake_backend_4_timer] |
| 37 | + Q --> R[Write to Wake Pipe] |
| 38 | + R --> S[Wake Up Backend Thread] |
| 39 | + S --> T[Process Timer Event] |
| 40 | + end |
| 41 | + |
| 42 | + R --> K --> F |
| 43 | +``` |
| 44 | + |
| 45 | +## Core Design Principles |
| 46 | + |
| 47 | +### 1. Dual-Thread Model |
| 48 | + |
| 49 | +- **UI Thread**: Responsible for Win32 message loop and GUI event handling |
| 50 | +- **Backend Thread**: Responsible for monitoring events in the asyncio event loop |
| 51 | + |
| 52 | +This separation allows the GUI to remain responsive while asynchronous operations run in the background. |
| 53 | + |
| 54 | +### 2. Thread Coordination Mechanisms |
| 55 | + |
| 56 | +```mermaid |
| 57 | +flowchart LR |
| 58 | + A[UI Thread] -->|releases| B[Semaphore] |
| 59 | + B -->|acquired by| C[Backend Thread] |
| 60 | + C -->|monitors events| D[epoll] |
| 61 | + D -->|events occur| E[Task Queue] |
| 62 | + E -->|processed by| A |
| 63 | + F[Timer Events] -->|writes to| G[Wake Pipe] |
| 64 | + G -->|wakes up| D |
| 65 | +``` |
| 66 | + |
| 67 | +1. **Semaphore**: Ensures the two threads execute alternately, preventing race conditions |
| 68 | +2. **Task Queue**: Thread-safe queue for passing functions that need to be executed on the UI thread |
| 69 | +3. **Wake Pipe**: Allows the backend thread to be awakened when new events (like timers) occur |
| 70 | + |
| 71 | +### 3. Message Passing Mechanisms |
| 72 | + |
| 73 | +```mermaid |
| 74 | +sequenceDiagram |
| 75 | + participant B as Backend Thread |
| 76 | + participant Q as Task Queue |
| 77 | + participant M as Message Window |
| 78 | + participant U as UI Thread |
| 79 | + |
| 80 | + B->>Q: Add function to queue |
| 81 | + B->>M: PostMessage(TRIO_MSG/ASYNCIO_MSG) |
| 82 | + M->>U: Process message |
| 83 | + U->>Q: Get and execute tasks |
| 84 | + U->>U: Run asyncio.run_once() |
| 85 | +``` |
| 86 | + |
| 87 | +1. **TRIO_MSG/ASYNCIO_MSG**: Custom Win32 message used to trigger asyncio task processing in the UI thread |
| 88 | +2. **PostMessage**: Safely sends messages from the backend thread to the UI thread |
| 89 | +3. **run_sync_soon_threadsafe**: Adds functions to the queue and notifies the UI thread to execute them |
| 90 | + |
| 91 | +## Key Event Flows |
| 92 | + |
| 93 | +### 1. Initialization Process |
| 94 | + |
| 95 | +```mermaid |
| 96 | +sequenceDiagram |
| 97 | + participant U as UI Thread |
| 98 | + participant S as Semaphore |
| 99 | + participant B as Backend Thread |
| 100 | + |
| 101 | + U->>U: Create coordination mechanisms |
| 102 | + U->>B: Start thread |
| 103 | + B->>S: Wait for semaphore |
| 104 | + U->>U: run_events_on_ui_thread() |
| 105 | + U->>S: Release semaphore |
| 106 | + S->>B: Semaphore acquired |
| 107 | + B->>B: Begin monitoring |
| 108 | +``` |
| 109 | + |
| 110 | +### 2. Normal Event Loop |
| 111 | + |
| 112 | +```mermaid |
| 113 | +sequenceDiagram |
| 114 | + participant U as UI Thread |
| 115 | + participant Q as Task Queue |
| 116 | + participant S as Semaphore |
| 117 | + participant B as Backend Thread |
| 118 | + |
| 119 | + U->>S: Release semaphore |
| 120 | + S->>B: Acquire semaphore |
| 121 | + B->>B: Call epoll.poll(timeout) |
| 122 | + Note over B: Event occurs |
| 123 | + B->>Q: Add task to queue |
| 124 | + B->>U: Send TRIO_MSG/ASYNCIO_MSG |
| 125 | + U->>Q: Process all tasks |
| 126 | + U->>U: Run asyncio.run_once() |
| 127 | + U->>S: Release semaphore |
| 128 | + S->>B: Acquire semaphore (loop continues) |
| 129 | +``` |
| 130 | + |
| 131 | +### 3. New Timer Event Flow |
| 132 | + |
| 133 | +```mermaid |
| 134 | +sequenceDiagram |
| 135 | + participant U as UI Thread |
| 136 | + participant T as Asyncio Timer |
| 137 | + participant W as Wake Pipe |
| 138 | + participant B as Backend Thread |
| 139 | + |
| 140 | + Note over U: loop.call_later() called |
| 141 | + U->>T: Create new timer |
| 142 | + T->>W: Write data to wake pipe |
| 143 | + W->>B: Wake up from epoll.poll() |
| 144 | + B->>U: Request process timer events |
| 145 | + U->>U: Process timer callbacks |
| 146 | +``` |
| 147 | + |
| 148 | +### 4. Timeout Handling Flow |
| 149 | + |
| 150 | +```mermaid |
| 151 | +sequenceDiagram |
| 152 | + participant U as UI Thread |
| 153 | + participant B as Backend Thread |
| 154 | + |
| 155 | + B->>B: Get timeout from loop.get_backend_timeout() |
| 156 | + B->>B: epoll.poll(timeout) |
| 157 | + Note over B: Timeout occurs |
| 158 | + B->>U: Request task processing |
| 159 | + U->>U: Execute expired timer callbacks |
| 160 | +``` |
| 161 | + |
| 162 | +### 5. Task Completion Flow |
| 163 | + |
| 164 | +```mermaid |
| 165 | +sequenceDiagram |
| 166 | + participant AT as Asyncio Task |
| 167 | + participant CB as Completion Callback |
| 168 | + participant U as UI Thread |
| 169 | + |
| 170 | + AT->>AT: Task completes |
| 171 | + AT->>CB: Call on_task_done |
| 172 | + CB->>U: Send result via run_sync_soon_threadsafe |
| 173 | + U->>U: Call done_callback |
| 174 | +``` |
| 175 | + |
| 176 | +## Implementation Considerations |
| 177 | + |
| 178 | +1. **Error Handling**: Both backend and UI threads need robust exception catching and handling |
| 179 | +2. **Resource Cleanup**: Properly close event loop, pipes, and epoll when tasks complete |
| 180 | +3. **Compatibility**: Handle API differences between asyncio versions |
| 181 | +4. **Deadlock Prevention**: Ensure semaphores are correctly released in all paths |
| 182 | +5. **Timer Precision**: Use wake mechanism to ensure timers are processed promptly |
| 183 | + |
| 184 | +## Key Function Descriptions |
| 185 | + |
| 186 | +### asyncio_guest_run |
| 187 | + |
| 188 | +Main entry function responsible for setting up the dual-thread environment and starting the asyncio task. |
| 189 | + |
| 190 | +### run_events_on_ui_thread |
| 191 | + |
| 192 | +Executes one cycle of the asyncio event loop on the UI thread, handling ready callbacks and I/O events. |
| 193 | + |
| 194 | +### backend_thread_loop |
| 195 | + |
| 196 | +Main loop of the backend thread, responsible for monitoring file descriptor events and timer timeouts. |
| 197 | + |
| 198 | +### wake_backend_4_timer |
| 199 | + |
| 200 | +Wakes up the backend thread when new timers are created, ensuring timer events are processed promptly. |
| 201 | + |
| 202 | +## Summary |
| 203 | + |
| 204 | +This design coordinates the Win32 message loop and asyncio event loop through careful orchestration, enabling the use of Python's asynchronous programming in GUI applications. Semaphores ensure synchronization between threads, while wake mechanisms and message queues provide efficient communication channels, solving the limitations of the traditional single-threaded asyncio model in GUI environments. |
0 commit comments