线程契约
哪些 API 能在哪个线程调、宿主做什么保证。
| API | 允许线程 | 宿主保证 |
|---|---|---|
initialize / shutdown / action_* | 任意(常为工作线程) | 不假设主线程;外部插件的 initialize 跑在后台线程 |
register_plugin_hotkey 等热键 API | 任意 | 内部切主线程再 RegisterHotKey |
feed_direct_caption / feed_caption | 任意 | 队列 / 信号进 UI |
translate / translate_batch / speak / stop_speak | 任意 | 内部切工作线程 |
emit_event / handle_host_event | 任意 | 线程安全 |
subscribe_event / unsubscribe_* | 任意 | 内部加锁 |
show_toast / show_error / set_window_excluded_from_capture | 任意 | 信号进 UI |
create_plugin_window 等浮窗 API | 任意 | 内部切 GUI 线程执行并等结果 |
pick_file / pick_files / pick_folder / pick_save_file | 任意 | 内部切 GUI 线程,会阻塞直到用户关闭对话框 |
begin_progress 系列 | 任意 | 加锁;pluginProgress 信号进 UI |
| 配置 / KV / 数据目录 | 任意 | 宿主串行化写入 |
QML invokePluginAction | 主线程 | 同步;耗时动作请用 invokePluginActionAsync 或 run_in_background |
硬性禁止
- ❌ 插件自行
ctypes.RegisterHotKey—— 绑错线程会静默失效。一律走宿主 API。 - ❌ 在
initialize/initialize里同步做耗时网络或推理(会拖慢甚至卡住调用方), 用self.run_in_background(...)。 - ❌ 在 QML 的
Component.onCompleted之外同步调长耗时插件动作。
推荐模式
耗时任务一律丢后台,结果用 emit_event 回 UI:
python
def action_ask(self, prompt="", **kwargs):
self.run_in_background(self._do_work, prompt)
def _do_work(self, prompt):
try:
result = self._call_api(prompt) # 慢
except Exception as exc:
self.host.show_error(f"请求失败:{exc}", source=self.id)
self.emit_event("stream_done", {"full_text": "", "error": str(exc)})
return
self.emit_event("stream_done", {"full_text": result})本地服务型插件还要注意:ensure_loaded() 是允许阻塞的(宿主已在后台线程调用), 但 initialize() / 只读方法不行——见 本地服务型引擎强制契约。
