插件應(yīng)該使用ApplicationManager.getApplication().invokeLater()
而不是標(biāo)準(zhǔn)的SwingUtilities.invokeLater()
傳遞控制從后臺(tái)線程到事件分發(fā)線程。這個(gè)API允許調(diào)用指定modality state,即允許調(diào)用執(zhí)行的模態(tài)對(duì)話框的堆棧。 傳遞ModalityState.NON_MODAL
意味著在所有模態(tài)對(duì)話框關(guān)閉后執(zhí)行操作。 傳遞ModalityState.stateForComponent()
意味著可以在指定的組件(對(duì)話框的一部分)仍然可見時(shí)執(zhí)行操作。
讀/寫鎖
通常情況下,*IntelliJ平臺(tái) *中代碼相關(guān)的數(shù)據(jù)結(jié)構(gòu)由一個(gè)單獨(dú)的讀寫鎖監(jiān)控。這適用于PSI,VFS和項(xiàng)目根模型。
可以從任何線程讀取數(shù)據(jù),從UI線程讀取數(shù)據(jù)不需要任何特殊的工作。但是,從任何其它線程的讀操作都需要使用ApplicationManager.getApplication().runReadAction()
包裹,或者簡短一點(diǎn)使用ReadAction.run/compute
。
只允許從UI線程寫數(shù)據(jù),寫操作必須被ApplicationManager.getApplication().runWriteAction()
包裹,或者簡短一點(diǎn)使用WriteAction.run/compute
。
除此之外,修改模型只允許從寫安全上下文中進(jìn)行,包括用戶操作,安全上下文中的invokeLater
調(diào)用(查閱下一部分)和事務(wù)(TransactionGuard.submitTransaction
)。你可能無法從UI渲染器或SwingUtilities.invokeLater
調(diào)用中修改PSI、VFS或項(xiàng)目模型。更多詳情請(qǐng)查閱TransactionGuard
文檔。
你不能在讀寫操作之外訪問模型。相關(guān)對(duì)象不能保證在幾個(gè)連續(xù)的讀取操作之間存在。 所以根據(jù)經(jīng)驗(yàn),每當(dāng)你開始一個(gè)讀取操作前都應(yīng)確認(rèn)說操作的PSI / VFS /項(xiàng)目/模塊是否仍然有效。
invokeLater
插件應(yīng)該使用ApplicationManager.getApplication().invokeLater()
而不是標(biāo)準(zhǔn)的SwingUtilities.invokeLater()
傳遞控制從后臺(tái)線程到事件分發(fā)線程。這個(gè)API允許調(diào)用指定模態(tài)狀態(tài),即允許調(diào)用執(zhí)行的模態(tài)對(duì)話框的堆棧。
- 傳遞
ModalityState.NON_MODAL
意味著在所有模態(tài)對(duì)話框關(guān)閉后執(zhí)行操作。注意這個(gè)狀態(tài)幾乎是不合適的,因?yàn)槿绻魏未蜷_的(不相關(guān)的)項(xiàng)目顯示一個(gè)每個(gè)項(xiàng)目的模式對(duì)話框,該操作將在其關(guān)閉后執(zhí)行。 - 傳遞
ModalityState.stateForComponent()
意味著當(dāng)頂層顯示的對(duì)話框包含指定組件或是指定組件父對(duì)話框之一時(shí)執(zhí)行操作。 - 如果沒有傳遞模態(tài)狀態(tài),
ModalityState.defaultModalityState()
將會(huì)被使用。大多數(shù)情況下這是最佳選擇,這時(shí)來自UI線程的調(diào)用將使用當(dāng)前模態(tài)狀態(tài),并且對(duì)用ProgressManager
啟動(dòng)的后臺(tái)進(jìn)程有特殊處理:來自此進(jìn)程的invokeLater
可能會(huì)運(yùn)行在進(jìn)程啟動(dòng)時(shí)的相同對(duì)話框。 -
ModalityState.any()
意味著runnable將會(huì)被盡快執(zhí)行而不管模態(tài)對(duì)話框。請(qǐng)注意此時(shí)runnable中禁止修改PSI、VFS或項(xiàng)目模型。更多詳情請(qǐng)查閱TransactionGuard
文檔。
如果你的UI線程活動(dòng)需要訪問基于文件的索引(如正在進(jìn)行項(xiàng)目范圍的PSI分析,解析引用等等),請(qǐng)使用DumbService#smartInvokeLater
。這樣,你的活動(dòng)將在所有可能的索引進(jìn)程完成后運(yùn)行。
防止UI凍結(jié)
后臺(tái)線程不應(yīng)執(zhí)行長時(shí)間的讀取操作。原因是如果UI線程需要寫操作(如用戶輸入某些東西),它必須盡快獲取控制權(quán),否則在所有后臺(tái)線程都釋放讀操作之前UI將被凍結(jié)。
最著名的方法是每當(dāng)即將發(fā)生寫操作時(shí)取消后臺(tái)讀取操作,稍后重新啟動(dòng)后臺(tái)讀取操作。 編輯器高亮顯示,代碼補(bǔ)全,轉(zhuǎn)到類/文件等操作都是這樣的。 有兩種推薦的方法:
- 如果你正在UI線程,插件一個(gè)
ReadTask
并將它傳到ProgressIndicatorUtils.schedule*
方法中的其中一個(gè)。在onCanceled
中, 如果活動(dòng)需要重新啟動(dòng)再次安排它執(zhí)行。 - 如果你已經(jīng)在后臺(tái)線程,在循環(huán)中使用
ProgressManager.getInstance().runInReadActionWithWriteActionPriority()
,直到它通過或整個(gè)活動(dòng)被廢棄。
在這兩種方法中,每個(gè)讀取操作的開始你應(yīng)該總是檢查所用對(duì)象是否仍然有效,整個(gè)操作是否仍然有意義(即未被用戶取消,項(xiàng)目未被關(guān)閉等)。
如果你正在進(jìn)行的活動(dòng)必須訪問基于文件的索引(如正在進(jìn)行項(xiàng)目范圍的PSI分析,解析引用等等),你應(yīng)該重寫ReadTask#runBackgroundProcess
并使用"smart-mode"讀取操作:DumbService.getInstance(project).runReadActionInSmartMode(() -> performInReadAction(indicator))
。