Skip to content

Commit 975dc29

Browse files
committed
async callback demo works
1 parent ffac5a9 commit 975dc29

File tree

5 files changed

+191
-262
lines changed

5 files changed

+191
-262
lines changed

examples/async_with_asyncio_guest_run/asyncio_guest_run.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ def schedule_coro():
1010
asyncio.run_coroutine_threadsafe(coro, loop)
1111
return schedule_coro
1212

13-
def asyncio_guest_run(async_func, *async_func_args, run_sync_soon_threadsafe, run_sync_soon_not_threadsafe, done_callback):
13+
def asyncio_guest_run(async_func, *async_func_args, run_sync_soon_threadsafe, run_sync_soon_not_threadsafe=None, done_callback):
1414
"""最简化的asyncio guest运行函数"""
1515
# 创建信号量用于线程协调
1616
sem = threading.Semaphore(0)
@@ -33,7 +33,7 @@ def process_events_on_ui(events):
3333
try:
3434
nonlocal count
3535
count += 1
36-
print(f"ui trigger number: {count}")
36+
print(f"ui trigger counter: {count}")
3737
if not loop.is_closed():
3838
# 处理事件和回调
3939
loop.process_events(events)

examples/async_with_asyncio_guest_run/bind_in_local_async.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,4 +213,4 @@ <h2>模拟异步API请求</h2>
213213
</div>
214214
</div>
215215
</body>
216-
</html>
216+
</html>

examples/async_with_asyncio_guest_run/bind_in_local_async.py

Lines changed: 0 additions & 63 deletions
This file was deleted.
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
#
2+
# Copyright 2020 Richard J. Sheridan
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#
16+
17+
from asyncio_guest_run import asyncio_guest_run, schedule_on_asyncio
18+
19+
import traceback
20+
from queue import Queue
21+
22+
import win32api
23+
import win32con
24+
import win32gui
25+
26+
#TODO why we need this?
27+
from outcome import Error
28+
29+
import asyncio
30+
from webview import Webview, SizeHint, Size
31+
32+
import os
33+
34+
ASYNCIO_MSG = win32con.WM_APP + 3
35+
36+
# 使用线程安全的 Queue 代替 deque
37+
trio_functions = Queue()
38+
39+
def do_trio():
40+
"""Process all pending trio tasks in the queue"""
41+
while not trio_functions.empty():
42+
try:
43+
# 获取并执行一个任务
44+
func = trio_functions.get()
45+
func()
46+
except Exception as e:
47+
print(rf"{__file__}:{do_trio.__name__} e: {e}")
48+
print(traceback.format_exc())
49+
raise e
50+
51+
class WebviewHost:
52+
def __init__(self, webview):
53+
self.webview = webview
54+
self.mainthreadid = win32api.GetCurrentThreadId()
55+
# create event queue with null op
56+
win32gui.PeekMessage(
57+
win32con.NULL, win32con.WM_USER, win32con.WM_USER, win32con.PM_NOREMOVE
58+
)
59+
self.create_message_window()
60+
61+
def create_message_window(self):
62+
# 注册窗口类
63+
wc = win32gui.WNDCLASS()
64+
wc.lpfnWndProc = self.trio_wndproc_func
65+
wc.lpszClassName = "TrioMessageWindow"
66+
win32gui.RegisterClass(wc)
67+
68+
# 创建隐藏窗口
69+
self.msg_hwnd = win32gui.CreateWindowEx(
70+
0, "TrioMessageWindow", "Trio Message Window",
71+
0, 0, 0, 0, 0, 0, 0, None, None
72+
)
73+
74+
def trio_wndproc_func(self, hwnd, msg, wparam, lparam):
75+
if msg == ASYNCIO_MSG:
76+
# 处理所有排队的 trio 任务
77+
do_trio()
78+
return 0
79+
# elif msg == DESTROY_WINDOW_MSG:
80+
# # 在正确的线程中销毁窗口
81+
# win32gui.DestroyWindow(hwnd)
82+
# return 0
83+
else:
84+
return win32gui.DefWindowProc(hwnd, msg, wparam, lparam)
85+
86+
def run_sync_soon_threadsafe(self, func):
87+
"""先添加函数到队列,后发送消息"""
88+
trio_functions.put(func)
89+
win32api.PostMessage(self.msg_hwnd, ASYNCIO_MSG, 0, 0)
90+
91+
def run_sync_soon_not_threadsafe(self, func):
92+
"""与 threadsafe 相同,保持一致性"""
93+
trio_functions.put(func)
94+
win32api.PostMessage(self.msg_hwnd, ASYNCIO_MSG, 0, 0)
95+
96+
def done_callback(self, outcome):
97+
"""non-blocking request to end the main loop"""
98+
print(f"Outcome: {outcome}")
99+
print(f"大功告成: {outcome}")
100+
if isinstance(outcome, Error):
101+
exc = outcome.error
102+
traceback.print_exception(type(exc), exc, exc.__traceback__)
103+
exitcode = 1
104+
else:
105+
exitcode = 0
106+
self.display.dialog.PostMessage(win32con.WM_CLOSE, 0, 0)
107+
self.display.dialog.close()
108+
# 通过消息请求主线程销毁窗口
109+
# win32api.PostMessage(self.msg_hwnd, DESTROY_WINDOW_MSG, 0, 0)
110+
win32gui.PostQuitMessage(exitcode)
111+
112+
def mainloop(self):
113+
self.webview.run()
114+
115+
# 异步函数示例 - 简单延迟响应
116+
async def delayed_response(seconds=1):
117+
await asyncio.sleep(seconds)
118+
return f"异步响应完成,耗时 {seconds} 秒"
119+
120+
# 异步函数示例 - 模拟进度报告
121+
async def process_with_progress(steps=5, step_time=1):
122+
results = []
123+
for i in range(1, steps + 1):
124+
await asyncio.sleep(step_time)
125+
# 通过JavaScript回调报告进度
126+
progress = (i / steps) * 100
127+
webview.eval(f"updateProgress({progress}, '处理中: 步骤 {i}/{steps}')")
128+
results.append(f"步骤 {i} 完成")
129+
130+
return {
131+
"status": "完成",
132+
"steps": steps,
133+
"results": results
134+
}
135+
136+
# 异步函数示例 - 模拟API请求
137+
async def fetch_data(delay=2, success=True):
138+
await asyncio.sleep(delay)
139+
140+
if not success:
141+
raise Exception("模拟的API请求失败")
142+
143+
return {
144+
"id": 123,
145+
"name": "示例数据",
146+
"timestamp": "2023-06-15T12:34:56Z",
147+
"items": [
148+
{"id": 1, "value": "项目 1"},
149+
{"id": 2, "value": "项目 2"},
150+
{"id": 3, "value": "项目 3"}
151+
]
152+
}
153+
154+
async def counter():
155+
count = 0
156+
while True:
157+
await asyncio.sleep(5)
158+
count += 1
159+
print(f"counter: {count}")
160+
161+
if __name__ == "__main__": # Create webview instance
162+
webview = Webview(debug=True)
163+
164+
# Bind Python functions
165+
webview.bind("delayedResponse", delayed_response)
166+
webview.bind("processWithProgress", process_with_progress)
167+
webview.bind("fetchData", fetch_data)
168+
169+
webview.title = "Python-JavaScript 异步绑定演示"
170+
webview.size = Size(800, 600, SizeHint.NONE)
171+
172+
# Get the absolute path to the HTML file
173+
current_dir = os.path.dirname(os.path.abspath(__file__))
174+
html_path = os.path.join(current_dir, 'bind_in_local_async.html')
175+
176+
# Load the local HTML file
177+
webview.navigate(f"file://{html_path}")
178+
179+
# --begin-- guest run
180+
host = WebviewHost(webview)
181+
asyncio_guest_run(
182+
counter,
183+
run_sync_soon_threadsafe=host.run_sync_soon_threadsafe,
184+
run_sync_soon_not_threadsafe=host.run_sync_soon_not_threadsafe,
185+
done_callback=host.done_callback,
186+
)
187+
host.mainloop()
188+
# --end-- guest run

0 commit comments

Comments
 (0)