插件應該使用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))
。