线程规则
读、写锁
提示
thread-access-info 插件可以在debug模式中显示读写操作和线程信息
一般情况下,只能通过一个单例的读写锁才能访问IntelliJ Platform 中的数据结构
在下面的子系统中,只能使用读写操作(ReadAction WriteAction)才能访问数据模型
读(Reading)
Reading 数据可以在任意线程中进行操作,代码如下:
ApplicationManager.getApplication().runReadAction(“读取数据”)
或者
ReadAction.run(“读取数据”)或者ReadAction.compute(“读取数据”)
在多个Reading 操作时,不能保证一个对象一直存活,所以建议在Reading之前,先检查PSI/VFS/project/module 是否存活
写(Writing)
Writing 数据只能在UI线程中操作,
ApplicationManager.getApplication().runWriteAction(“写数据”)
或者
WriteAction run(“写数据”)/compute(“写数据”)
只有在 write-safe 环境中才能修改数据模型,在UI渲染器中和SwingUtilities.invokeLater()方法中,都不能修改PSI/VFS/project/module
后台进程和取消进程异常
后台进程由ProgressManager 进行管理,他提供了很多方法来后台执行进程,例如 对话框进程,状态栏显示进程,或者隐藏进程
在当前线路中,通过 ProgressIndicator progressIndicator = ProgressIndicatorProvider.getGlobalProgressIndicator() 获取当前进程对象
对于可见进程,可以通过ProgressIndicator通知用户当前进程的状态,例如修改text或者进度百分比
用户通过Cancel按钮或者通过代码ProgressIndicator.cancel() 可以取消进程,取消进程后,调用ProgressIndicator.checkCanceled()或者 ProgressManager.checkCanceled()将会抛出 ProcessCanceledException 异常
在后台进程中需要随时处理 ProcessCanceledException异常,处理时只需要重新抛出就行,在启动线程时会处理该异常
应该经常调用 checkCanceled()保证进程能顺序取消,在PSI的方法中有大量的checkCanceled()调用,如果在后台进程中,有大量的非PSI的方法调用,就要经常调用checkCanceled(),例如在执行循环的时候调用
取消读操作
后台进程不应该进行长时间的读操作,因为如果用户输入了某些东西,这时候UI线程需要尽快获取写锁,但是线程会等到读锁被释放后才能获取到写锁
最好的方法是当需要获取写锁的时候,停止读操作,当写操作完成的时候,再重新开始读操作,例如 搜索高亮,代码补全,Goto Class/File等操作
为了实现这一点,冗长的后台操作应该先获取 ProgressIndicator(ProgressIndicator progressIndicator = ProgressIndicatorProvider.getGlobalProgressIndicator()),当准备执行写操作的时候,调用progressIndicator.cancel() ,这样在下次调用checkCanceled()时,程序会抛出ProcessCanceledException异常,线程就会尽快停止它的读操作
推荐下面2种方法保证上述操作
如果在UI线程里进行读操作,使用 NonBlockingReadAction<Void> voidNonBlockingReadAction = ReadAction.nonBlocking(() -> {});
如果已经在后台线程中,循环调用 ProgressManager.getInstance().runInReadActionWithWriteActionPriority()直到读取完成或停止
上面的2个方法,会在每一次读操作之前执行检查,如果需要停止读操作,调用 expireWith() 或 expireWhen()方法
如果需要访问 文件索引 (例如用来分析引用关系),使用 ReadAction.nonBlocking(...).inSmartMode() 方法
ProgressIndicator progressIndicator = ProgressIndicatorProvider.getGlobalProgressIndicator();
progressIndicator.checkCanceled();
NonBlockingReadAction<Void> voidNonBlockingReadAction = ReadAction.nonBlocking(() -> {
});
voidNonBlockingReadAction.expireWith(...);
避免UI线程阻塞
不要在UI线程中执行耗时长的操作
特别是,不要遍历虚拟文件系统(Virtual File System) 、解析 Program Structure Interface (PSI) 、解析引用 或查询文件索引 。
WriteActions 必须发生在UI线程上,所以需要加快它的速度,你可以WriteActions之外的操作移动到预操作里,在后台线程里执行(例如上面的ReadAction.nonBlocking())
不要在事件监听器内部做耗时长的操作,理想情况下,事件监听器内应该只清理缓存。也可以在后台进程中执行事件,但需要注意的是在后台处理开始之前可能会传递一些新事件,导致环境可能发生变化,考虑使用MergingUpdateQueue 和 ReadAction.nonBlocking() 避免这类问题
大部的VFS 事件在后面进程中预执行,具体可查看 AsyncFileListener