线程规则

走着路睡觉大约 4 分钟

读、写锁

提示

thread-access-infoopen in new window 插件可以在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

后台进程和取消进程异常

后台进程由ProgressManageropen in new window 进行管理,他提供了很多方法来后台执行进程,例如 对话框进程,状态栏显示进程,或者隐藏进程
在当前线路中,通过 ProgressIndicator progressIndicator = ProgressIndicatorProvider.getGlobalProgressIndicator() 获取当前进程对象

对于可见进程,可以通过ProgressIndicator通知用户当前进程的状态,例如修改text或者进度百分比

用户通过Cancel按钮或者通过代码ProgressIndicator.cancel() 可以取消进程,取消进程后,调用ProgressIndicator.checkCanceled()或者 ProgressManager.checkCanceled()将会抛出 ProcessCanceledExceptionopen in new window 异常

在后台进程中需要随时处理 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())

不要在事件监听器内部做耗时长的操作,理想情况下,事件监听器内应该只清理缓存。也可以在后台进程中执行事件,但需要注意的是在后台处理开始之前可能会传递一些新事件,导致环境可能发生变化,考虑使用MergingUpdateQueueopen in new window 和 ReadAction.nonBlocking() 避免这类问题

大部的VFS 事件在后面进程中预执行,具体可查看 AsyncFileListeneropen in new window

上次编辑于:
贡献者: zhaojingbo
Loading...