diff --git a/src/main/kotlin/com/chylex/intellij/inspectionlens/InspectionLensRefresher.kt b/src/main/kotlin/com/chylex/intellij/inspectionlens/InspectionLensRefresher.kt
new file mode 100644
index 0000000..c9558bf
--- /dev/null
+++ b/src/main/kotlin/com/chylex/intellij/inspectionlens/InspectionLensRefresher.kt
@@ -0,0 +1,25 @@
+package com.chylex.intellij.inspectionlens
+
+import com.intellij.openapi.application.ApplicationManager
+
+object InspectionLensRefresher {
+	private var needsRefresh = false
+	
+	fun scheduleRefresh() {
+		synchronized(this) {
+			if (!needsRefresh) {
+				needsRefresh = true
+				ApplicationManager.getApplication().invokeLater(::refresh)
+			}
+		}
+	}
+	
+	private fun refresh() {
+		synchronized(this) {
+			if (needsRefresh) {
+				needsRefresh = false
+				InspectionLens.refresh()
+			}
+		}
+	}
+}
diff --git a/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/EditorLensManager.kt b/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/EditorLensManager.kt
index 1343fd2..1bd3873 100644
--- a/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/EditorLensManager.kt
+++ b/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/EditorLensManager.kt
@@ -30,6 +30,10 @@ class EditorLensManager private constructor(private val editor: Editor) {
 	private fun show(highlighterWithInfo: HighlighterWithInfo) {
 		val (highlighter, info) = highlighterWithInfo
 		
+		if (!highlighter.isValid) {
+			return
+		}
+		
 		val existingLens = lenses[highlighter]
 		if (existingLens != null) {
 			if (existingLens.update(info)) {
@@ -48,22 +52,10 @@ class EditorLensManager private constructor(private val editor: Editor) {
 		}
 	}
 	
-	fun show(highlightersWithInfo: Collection<HighlighterWithInfo>) {
-		executeInBatchMode(highlightersWithInfo.size) {
-			highlightersWithInfo.forEach(::show)
-		}
-	}
-	
 	private fun hide(highlighter: RangeHighlighter) {
 		lenses.remove(highlighter)?.hide()
 	}
 	
-	fun hide(highlighters: Collection<RangeHighlighter>) {
-		executeInBatchMode(highlighters.size) {
-			highlighters.forEach(::hide)
-		}
-	}
-	
 	fun hideAll() {
 		executeInBatchMode(lenses.size) {
 			lenses.values.forEach(EditorLens::hide)
@@ -71,6 +63,28 @@ class EditorLensManager private constructor(private val editor: Editor) {
 		}
 	}
 	
+	fun execute(commands: Collection<Command>) {
+		executeInBatchMode(commands.size) {
+			commands.forEach { it.apply(this) }
+		}
+	}
+	
+	sealed interface Command {
+		fun apply(lensManager: EditorLensManager)
+		
+		class Show(private val highlighter: HighlighterWithInfo) : Command {
+			override fun apply(lensManager: EditorLensManager) {
+				lensManager.show(highlighter)
+			}
+		}
+		
+		class Hide(private val highlighter: RangeHighlighter) : Command {
+			override fun apply(lensManager: EditorLensManager) {
+				lensManager.hide(highlighter)
+			}
+		}
+	}
+	
 	/**
 	 * Batch mode affects both inlays and highlighters used for line colors.
 	 */
diff --git a/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/EditorLensManagerDispatcher.kt b/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/EditorLensManagerDispatcher.kt
new file mode 100644
index 0000000..6db54b7
--- /dev/null
+++ b/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/EditorLensManagerDispatcher.kt
@@ -0,0 +1,41 @@
+package com.chylex.intellij.inspectionlens.editor
+
+import com.intellij.openapi.application.ApplicationManager
+import com.intellij.openapi.editor.markup.RangeHighlighter
+
+class EditorLensManagerDispatcher(private val lensManager: EditorLensManager) {
+	private var queuedItems = mutableListOf<EditorLensManager.Command>()
+	private var isEnqueued = false
+	
+	fun show(highlighterWithInfo: HighlighterWithInfo) {
+		enqueue(EditorLensManager.Command.Show(highlighterWithInfo))
+	}
+	
+	fun hide(highlighter: RangeHighlighter) {
+		enqueue(EditorLensManager.Command.Hide(highlighter))
+	}
+	
+	private fun enqueue(item: EditorLensManager.Command) {
+		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<EditorLensManager.Command>
+		
+		synchronized(this) {
+			itemsToProcess = queuedItems
+			queuedItems = mutableListOf()
+			isEnqueued = false
+		}
+		
+		lensManager.execute(itemsToProcess)
+	}
+}
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 b368f59..8a2fa22 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,5 @@
 package com.chylex.intellij.inspectionlens.editor
 
-import com.chylex.intellij.inspectionlens.utils.DebouncingInvokeOnDispatchThread
 import com.intellij.codeInsight.daemon.impl.HighlightInfo
 import com.intellij.lang.annotation.HighlightSeverity
 import com.intellij.openapi.Disposable
@@ -17,10 +16,7 @@ import com.intellij.openapi.util.Key
  * Listens for inspection highlights and reports them to [EditorLensManager].
  */
 internal class LensMarkupModelListener private constructor(editor: Editor) : MarkupModelListener {
-	private val lensManager = EditorLensManager.getOrCreate(editor)
-	
-	private val showOnDispatchThread = DebouncingInvokeOnDispatchThread(lensManager::show)
-	private val hideOnDispatchThread = DebouncingInvokeOnDispatchThread(lensManager::hide)
+	private val lensManagerDispatcher = EditorLensManagerDispatcher(EditorLensManager.getOrCreate(editor))
 	
 	override fun afterAdded(highlighter: RangeHighlighterEx) {
 		showIfValid(highlighter)
@@ -32,18 +28,18 @@ internal class LensMarkupModelListener private constructor(editor: Editor) : Mar
 	
 	override fun beforeRemoved(highlighter: RangeHighlighterEx) {
 		if (getFilteredHighlightInfo(highlighter) != null) {
-			hideOnDispatchThread.enqueue(highlighter)
+			lensManagerDispatcher.hide(highlighter)
 		}
 	}
 	
 	private fun showIfValid(highlighter: RangeHighlighter) {
-		runWithHighlighterIfValid(highlighter, showOnDispatchThread::enqueue, ::showAsynchronously)
+		runWithHighlighterIfValid(highlighter, lensManagerDispatcher::show, ::showAsynchronously)
 	}
 	
 	private fun showAsynchronously(highlighterWithInfo: HighlighterWithInfo.Async) {
 		highlighterWithInfo.requestDescription {
 			if (highlighterWithInfo.highlighter.isValid && highlighterWithInfo.hasDescription) {
-				showOnDispatchThread.enqueue(highlighterWithInfo)
+				lensManagerDispatcher.show(highlighterWithInfo)
 			}
 		}
 	}
diff --git a/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/LensSeverity.kt b/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/LensSeverity.kt
index 412c7c0..e452a4f 100644
--- a/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/LensSeverity.kt
+++ b/src/main/kotlin/com/chylex/intellij/inspectionlens/editor/LensSeverity.kt
@@ -1,7 +1,6 @@
 package com.chylex.intellij.inspectionlens.editor
 
-import com.chylex.intellij.inspectionlens.InspectionLens
-import com.chylex.intellij.inspectionlens.utils.DebouncingInvokeOnDispatchThread
+import com.chylex.intellij.inspectionlens.InspectionLensRefresher
 import com.intellij.lang.annotation.HighlightSeverity
 import com.intellij.spellchecker.SpellCheckerSeveritiesProvider
 import com.intellij.ui.ColorUtil
@@ -47,14 +46,12 @@ enum class LensSeverity(baseColor: Color, lightThemeDarkening: Int, darkThemeBri
 			SpellCheckerSeveritiesProvider.TYPO               to TYPO,
 		))
 		
-		private val refreshLater = DebouncingInvokeOnDispatchThread<Unit> { InspectionLens.refresh() }
-		
 		/**
 		 * Registers a mapping from a [HighlightSeverity] to a [LensSeverity], and refreshes all open editors.
 		 */
 		internal fun registerMapping(severity: HighlightSeverity, lensSeverity: LensSeverity) {
 			if (mapping.put(severity, lensSeverity) != lensSeverity) {
-				refreshLater.enqueue(Unit)
+				InspectionLensRefresher.scheduleRefresh()
 			}
 		}
 		
diff --git a/src/main/kotlin/com/chylex/intellij/inspectionlens/utils/DebouncingInvokeOnDispatchThread.kt b/src/main/kotlin/com/chylex/intellij/inspectionlens/utils/DebouncingInvokeOnDispatchThread.kt
deleted file mode 100644
index cae2a45..0000000
--- a/src/main/kotlin/com/chylex/intellij/inspectionlens/utils/DebouncingInvokeOnDispatchThread.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-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)
-	}
-}