Skip to content

线程契约 ​

哪些 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() / 只读方法不行——见 本地服务型引擎强制契约。


相关 ​

TransBox - 赋能全人类无界限跨语言交流