diff --git a/src/event/DeferEvent.cxx b/src/event/DeferEvent.cxx
index 518aa0a1a..433b432fa 100644
--- a/src/event/DeferEvent.cxx
+++ b/src/event/DeferEvent.cxx
@@ -21,3 +21,12 @@ DeferEvent::ScheduleIdle() noexcept
 
 	assert(IsPending());
 }
+
+void
+DeferEvent::ScheduleNext() noexcept
+{
+	if (!IsPending())
+		loop.AddNext(*this);
+
+	assert(IsPending());
+}
diff --git a/src/event/DeferEvent.hxx b/src/event/DeferEvent.hxx
index fa8432f1c..cc5c1fdf9 100644
--- a/src/event/DeferEvent.hxx
+++ b/src/event/DeferEvent.hxx
@@ -49,6 +49,15 @@ public:
 	 */
 	void ScheduleIdle() noexcept;
 
+	/**
+	 * Schedule this event, but only after the next #EventLoop
+	 * iteration (i.e. after the epoll_wait() call, after handling
+	 * all pending I/O events).  This is useful for repeated I/O
+	 * operations that should not occupy the whole #EventLoop,
+	 * starving all other I/O events.
+	 */
+	void ScheduleNext() noexcept;
+
 	void Cancel() noexcept {
 		if (IsPending())
 			unlink();
diff --git a/src/event/Loop.cxx b/src/event/Loop.cxx
index a73e1e963..6d3cf7c2b 100644
--- a/src/event/Loop.cxx
+++ b/src/event/Loop.cxx
@@ -45,6 +45,7 @@ EventLoop::~EventLoop() noexcept
 
 	assert(defer.empty());
 	assert(idle.empty());
+	assert(next.empty());
 #ifdef HAVE_THREADED_EVENT_LOOP
 	assert(inject.empty());
 #endif
@@ -201,6 +202,14 @@ EventLoop::AddIdle(DeferEvent &e) noexcept
 #endif
 }
 
+void
+EventLoop::AddNext(DeferEvent &e) noexcept
+{
+	assert(IsInside());
+
+	next.push_back(e);
+}
+
 void
 EventLoop::RunDeferred() noexcept
 {
@@ -295,7 +304,7 @@ EventLoop::Run() noexcept
 
 		/* invoke timers */
 
-		const auto timeout = HandleTimers();
+		Event::Duration timeout = HandleTimers();
 		if (quit)
 			break;
 
@@ -331,8 +340,13 @@ EventLoop::Run() noexcept
 
 		/* wait for new event */
 
+		if (!next.empty())
+			timeout = Event::Duration{0};
+
 		Wait(timeout);
 
+		idle.splice(std::next(idle.begin()), next);
+
 		FlushClockCaches();
 
 #ifdef HAVE_THREADED_EVENT_LOOP
diff --git a/src/event/Loop.hxx b/src/event/Loop.hxx
index d2a551da5..a904f15de 100644
--- a/src/event/Loop.hxx
+++ b/src/event/Loop.hxx
@@ -67,6 +67,12 @@ class EventLoop final
 	 */
 	DeferList idle;
 
+	/**
+	 * This is like #idle, but gets invoked after the next
+	 * epoll_wait() call.
+	 */
+	DeferList next;
+
 #ifdef HAVE_THREADED_EVENT_LOOP
 	Mutex mutex;
 
@@ -226,6 +232,7 @@ public:
 	 */
 	void AddDefer(DeferEvent &e) noexcept;
 	void AddIdle(DeferEvent &e) noexcept;
+	void AddNext(DeferEvent &e) noexcept;
 
 #ifdef HAVE_THREADED_EVENT_LOOP
 	/**