通用线程规则
插件应该使用ApplicationManager.getApplication().invokeLater()
而不是标准的SwingUtilities.invokeLater()
传递控制从后台线程到事件分发线程。这个API允许调用指定modality state,即允许调用执行的模态对话框的堆栈。 传递ModalityState.NON_MODAL
意味着在所有模态对话框关闭后执行操作。 传递ModalityState.stateForComponent()
意味着可以在指定的组件(对话框的一部分)仍然可见时执行操作。
读/写锁
通常情况下,*IntelliJ平台 *中代码相关的数据结构由一个单独的读写锁监控。这适用于PSI,VFS和项目根模型。
可以从任何线程读取数据,从UI线程读取数据不需要任何特殊的工作。但是,从任何其它线程的读操作都需要使用ApplicationManager.getApplication().runReadAction()
包裹,或者简短一点使用ReadAction.run/compute
。
只允许从UI线程写数据,写操作必须被ApplicationManager.getApplication().runWriteAction()
包裹,或者简短一点使用WriteAction.run/compute
。
除此之外,修改模型只允许从写安全上下文中进行,包括用户操作,安全上下文中的invokeLater
调用(查阅下一部分)和事务(TransactionGuard.submitTransaction
)。你可能无法从UI渲染器或SwingUtilities.invokeLater
调用中修改PSI、VFS或项目模型。更多详情请查阅TransactionGuard
文档。
你不能在读写操作之外访问模型。相关对象不能保证在几个连续的读取操作之间存在。 所以根据经验,每当你开始一个读取操作前都应确认说操作的PSI / VFS /项目/模块是否仍然有效。
invokeLater
插件应该使用ApplicationManager.getApplication().invokeLater()
而不是标准的SwingUtilities.invokeLater()
传递控制从后台线程到事件分发线程。这个API允许调用指定模态状态,即允许调用执行的模态对话框的堆栈。
- 传递
ModalityState.NON_MODAL
意味着在所有模态对话框关闭后执行操作。注意这个状态几乎是不合适的,因为如果任何打开的(不相关的)项目显示一个每个项目的模式对话框,该操作将在其关闭后执行。 - 传递
ModalityState.stateForComponent()
意味着当顶层显示的对话框包含指定组件或是指定组件父对话框之一时执行操作。 - 如果没有传递模态状态,
ModalityState.defaultModalityState()
将会被使用。大多数情况下这是最佳选择,这时来自UI线程的调用将使用当前模态状态,并且对用ProgressManager
启动的后台进程有特殊处理:来自此进程的invokeLater
可能会运行在进程启动时的相同对话框。 -
ModalityState.any()
意味着runnable将会被尽快执行而不管模态对话框。请注意此时runnable中禁止修改PSI、VFS或项目模型。更多详情请查阅TransactionGuard
文档。
如果你的UI线程活动需要访问基于文件的索引(如正在进行项目范围的PSI分析,解析引用等等),请使用DumbService#smartInvokeLater
。这样,你的活动将在所有可能的索引进程完成后运行。
防止UI冻结
后台线程不应执行长时间的读取操作。原因是如果UI线程需要写操作(如用户输入某些东西),它必须尽快获取控制权,否则在所有后台线程都释放读操作之前UI将被冻结。
最著名的方法是每当即将发生写操作时取消后台读取操作,稍后重新启动后台读取操作。 编辑器高亮显示,代码补全,转到类/文件等操作都是这样的。 有两种推荐的方法:
- 如果你正在UI线程,插件一个
ReadTask
并将它传到ProgressIndicatorUtils.schedule*
方法中的其中一个。在onCanceled
中, 如果活动需要重新启动再次安排它执行。 - 如果你已经在后台线程,在循环中使用
ProgressManager.getInstance().runInReadActionWithWriteActionPriority()
,直到它通过或整个活动被废弃。
在这两种方法中,每个读取操作的开始你应该总是检查所用对象是否仍然有效,整个操作是否仍然有意义(即未被用户取消,项目未被关闭等)。
如果你正在进行的活动必须访问基于文件的索引(如正在进行项目范围的PSI分析,解析引用等等),你应该重写ReadTask#runBackgroundProcess
并使用"smart-mode"读取操作:DumbService.getInstance(project).runReadActionInSmartMode(() -> performInReadAction(indicator))
。