线程模型、读写动作与后台任务¶
IntelliJ Platform 是高度并发的环境。插件代码必须尊重平台线程模型,否则很容易造成 UI 卡顿、死锁、PSI 失效或随机异常。
两类主要线程¶
| 线程 | 作用 | 注意事项 |
|---|---|---|
| EDT | UI 事件、UI 更新、写入数据模型 | 必须快;不要做耗时工作 |
| BGT | 后台计算、长任务、索引查询 | 读取模型要使用 Read Action |
EDT 只有一个,BGT 可以有多个。耗时操作放到后台,UI 更新回到 EDT。
读写锁¶
平台数据结构,例如 PSI、VFS、项目模型,不是普通意义上的线程安全对象。访问它们需要通过平台读写锁:
- Read Action:读取 PSI/VFS/项目模型。
- Write Action:修改 PSI/VFS/项目模型。
- Write Intent:平台内部用于协调可能升级为写入的访问。
插件不要手动管理底层锁,应使用平台提供的 Read/Write Action API。
Read Action¶
val result = ReadAction.compute<String, Throwable> {
psiFile.text
}
原则:
- 后台线程读取 PSI 时必须包 Read Action。
- Read Action 包裹范围越小越好。
- 长时间读操作考虑 Non-blocking Read Action,避免阻塞写入和 UI。
- 两次 Read Action 之间对象可能失效,要检查
isValid。
Write Action¶
WriteCommandAction.runWriteCommandAction(project) {
psiElement.replace(newElement)
}
原则:
- 修改 PSI、VFS、项目模型必须使用 Write Action。
- 用户可撤销的代码修改优先用
WriteCommandAction。 - 不要在 UI renderer 或任意
SwingUtilities.invokeLater()中修改模型。 - 对 2023.3+ 平台,涉及模型写入时优先使用
Application.invokeLater()安排到合适的 modality。
后台任务¶
耗时任务可以使用 Task.Backgroundable:
ProgressManager.getInstance().run(
object : Task.Backgroundable(project, "Analyzing Project", true) {
override fun run(indicator: ProgressIndicator) {
indicator.text = "Collecting files"
// long-running work
}
}
)
建议:
- 定期检查
indicator.checkCanceled()。 - 不要在后台线程直接更新 Swing UI。
- 读取 PSI 使用 Read Action。
- 写入模型拆成短小 Write Action。
Action 线程选择¶
Action 的 update() 必须声明线程:
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
选择规则:
- 需要读 PSI、VFS、项目模型:
BGT。 - 需要访问 Swing UI 状态:
EDT。 update()不能执行耗时操作。