diff --git a/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/EditorInlayLensManager.kt b/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/EditorInlayLensManager.kt
index 97f67c0..189847f 100644
--- a/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/EditorInlayLensManager.kt
+++ b/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/EditorInlayLensManager.kt
@@ -50,7 +50,7 @@ class EditorInlayLensManager private constructor(private val editor: Editor) {
 	
 	private val inlays = mutableMapOf<RangeHighlighter, Inlay<LensRenderer>>()
 	
-	fun show(highlighterWithInfo: HighlighterWithInfo) {
+	private fun show(highlighterWithInfo: HighlighterWithInfo) {
 		val (highlighter, info) = highlighterWithInfo
 		val currentInlay = inlays[highlighter]
 		if (currentInlay != null && currentInlay.isValid) {
@@ -73,10 +73,14 @@ class EditorInlayLensManager private constructor(private val editor: Editor) {
 		executeInInlayBatchMode(highlightersWithInfo.size) { highlightersWithInfo.forEach(::show) }
 	}
 	
-	fun hide(highlighter: RangeHighlighter) {
+	private fun hide(highlighter: RangeHighlighter) {
 		inlays.remove(highlighter)?.dispose()
 	}
 	
+	fun hideAll(highlighters: Collection<RangeHighlighter>) {
+		executeInInlayBatchMode(highlighters.size) { highlighters.forEach(::hide) }
+	}
+	
 	fun hideAll() {
 		executeInInlayBatchMode(inlays.size) { inlays.values.forEach(Inlay<*>::dispose) }
 		inlays.clear()
diff --git a/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/LensMarkupModelListener.kt b/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/LensMarkupModelListener.kt
index 6fc76b6..603ea65 100644
--- a/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/LensMarkupModelListener.kt
+++ b/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/LensMarkupModelListener.kt
@@ -1,6 +1,7 @@
 package com.chylex.intellij.inspectionlens.editor
 
 import com.chylex.intellij.inspectionlens.InspectionLensPluginDisposableService
+import com.chylex.intellij.inspectionlens.utils.DebouncingInvokeOnDispatchThread
 import com.intellij.codeInsight.daemon.impl.HighlightInfo
 import com.intellij.lang.annotation.HighlightSeverity
 import com.intellij.openapi.application.ApplicationManager
@@ -21,6 +22,9 @@ import com.jetbrains.rd.util.lifetime.intersect
 class LensMarkupModelListener private constructor(editor: Editor) : MarkupModelListener {
 	private val lens = EditorInlayLensManager.getOrCreate(editor)
 	
+	private val showOnDispatchThread = DebouncingInvokeOnDispatchThread(lens::showAll)
+	private val hideOnDispatchThread = DebouncingInvokeOnDispatchThread(lens::hideAll)
+	
 	override fun afterAdded(highlighter: RangeHighlighterEx) {
 		showIfValid(highlighter)
 	}
@@ -30,35 +34,19 @@ class LensMarkupModelListener private constructor(editor: Editor) : MarkupModelL
 	}
 	
 	override fun beforeRemoved(highlighter: RangeHighlighterEx) {
-		lens.hide(highlighter)
+		if (getFilteredHighlightInfo(highlighter) != null) {
+			hideOnDispatchThread.enqueue(highlighter)
+		}
 	}
 	
 	private fun showIfValid(highlighter: RangeHighlighter) {
-		runWithHighlighterIfValid(highlighter, lens::show, ::showAsynchronously)
-	}
-	
-	private fun showAllValid(highlighters: Array<RangeHighlighter>) {
-		val immediateHighlighters = mutableListOf<HighlighterWithInfo>()
-		
-		for (highlighter in highlighters) {
-			runWithHighlighterIfValid(highlighter, immediateHighlighters::add, ::showAsynchronously)
-		}
-		
-		lens.showAll(immediateHighlighters)
+		runWithHighlighterIfValid(highlighter, showOnDispatchThread::enqueue, ::showAsynchronously)
 	}
 	
 	private fun showAsynchronously(highlighterWithInfo: HighlighterWithInfo.Async) {
 		highlighterWithInfo.requestDescription {
 			if (highlighterWithInfo.highlighter.isValid && highlighterWithInfo.hasDescription) {
-				val application = ApplicationManager.getApplication()
-				if (application.isDispatchThread) {
-					lens.show(highlighterWithInfo)
-				}
-				else {
-					application.invokeLater {
-						lens.show(highlighterWithInfo)
-					}
-				}
+				showOnDispatchThread.enqueue(highlighterWithInfo)
 			}
 		}
 	}
@@ -66,15 +54,12 @@ class LensMarkupModelListener private constructor(editor: Editor) : MarkupModelL
 	companion object {
 		private val MINIMUM_SEVERITY = HighlightSeverity.TEXT_ATTRIBUTES.myVal + 1
 		
-		private fun getHighlightInfoIfValid(highlighter: RangeHighlighter): HighlightInfo? {
-			return if (highlighter.isValid)
-				HighlightInfo.fromRangeHighlighter(highlighter)?.takeIf { it.severity.myVal >= MINIMUM_SEVERITY }
-			else
-				null
+		private fun getFilteredHighlightInfo(highlighter: RangeHighlighter): HighlightInfo? {
+			return HighlightInfo.fromRangeHighlighter(highlighter)?.takeIf { it.severity.myVal >= MINIMUM_SEVERITY }
 		}
 		
 		private inline fun runWithHighlighterIfValid(highlighter: RangeHighlighter, actionForImmediate: (HighlighterWithInfo) -> Unit, actionForAsync: (HighlighterWithInfo.Async) -> Unit) {
-			val info = getHighlightInfoIfValid(highlighter)
+			val info = highlighter.takeIf { it.isValid }?.let(::getFilteredHighlightInfo)
 			if (info != null) {
 				processHighlighterWithInfo(HighlighterWithInfo.from(highlighter, info), actionForImmediate, actionForAsync)
 			}
@@ -91,7 +76,7 @@ class LensMarkupModelListener private constructor(editor: Editor) : MarkupModelL
 		
 		/**
 		 * Attaches a new [LensMarkupModelListener] to the document model of the provided [TextEditor], and reports all existing inspection highlights to [EditorInlayLensManager].
-		 * 
+		 *
 		 * The [LensMarkupModelListener] will be disposed when either the [TextEditor] is disposed, or via [InspectionLensPluginDisposableService] when the plugin is unloaded.
 		 */
 		fun install(textEditor: TextEditor) {
@@ -103,7 +88,10 @@ class LensMarkupModelListener private constructor(editor: Editor) : MarkupModelL
 				
 				val listener = LensMarkupModelListener(editor)
 				markupModel.addMarkupModelListener(pluginLifetime.intersect(editorLifetime).createNestedDisposable(), listener)
-				listener.showAllValid(markupModel.allHighlighters)
+				
+				for (highlighter in markupModel.allHighlighters) {
+					listener.showIfValid(highlighter)
+				}
 			}
 		}
 	}
diff --git a/src/main/kotlin/com/chylex/intellij/inspectionlens/utils/DebouncingInvokeOnDispatchThread.kt b/src/main/kotlin/com/chylex/intellij/inspectionlens/utils/DebouncingInvokeOnDispatchThread.kt
new file mode 100644
index 0000000..cae2a45
--- /dev/null
+++ b/src/main/kotlin/com/chylex/intellij/inspectionlens/utils/DebouncingInvokeOnDispatchThread.kt
@@ -0,0 +1,32 @@
+package com.chylex.intellij.inspectionlens.utils
+
+import com.intellij.openapi.application.ApplicationManager
+
+class DebouncingInvokeOnDispatchThread<T>(private val action: (List<T>) -> Unit) {
+	private var queuedItems = mutableListOf<T>()
+	private var isEnqueued = false
+	
+	fun enqueue(item: T) {
+		synchronized(this) {
+			queuedItems.add(item)
+			
+			// Enqueue even if already on dispatch thread to debounce consecutive calls.
+			if (!isEnqueued) {
+				isEnqueued = true
+				ApplicationManager.getApplication().invokeLater(::process)
+			}
+		}
+	}
+	
+	private fun process() {
+		var itemsToProcess: List<T>
+		
+		synchronized(this) {
+			itemsToProcess = queuedItems
+			queuedItems = mutableListOf()
+			isEnqueued = false
+		}
+		
+		action(itemsToProcess)
+	}
+}