diff --git a/emper/Actor.hpp b/emper/Actor.hpp
index 3b95c0d673c0b0c5075eb5478eda41eee10030f0..80fa5f7b25c42cc748ddb53838c21d92ee3f28f5 100644
--- a/emper/Actor.hpp
+++ b/emper/Actor.hpp
@@ -8,7 +8,7 @@
 #include "CallerEnvironment.hpp"
 #include "Fiber.hpp"
 #include "UnboundedBlockingMpscQueue.hpp"
-#include "io/Future.hpp"
+#include "emper.hpp"
 
 template <typename T>
 class Actor {
@@ -78,30 +78,22 @@ class Actor {
 	auto pendingMailboxItems() -> size_t { return queue.size(); }
 
 	auto waitUntilIdle(long timeout) -> bool {
-		if constexpr (emper::IO) {
-			for (; timeout > 0; --timeout) {
-				emper::io::AlarmFuture::Timespec ts = {.tv_sec = 1, .tv_nsec = 0};
-				emper::io::AlarmFuture alarm(ts);
-				alarm.submitAndWait();
-				if (queue.size() == 0 && state.load(std::memory_order_acquire) == Retrieving) {
-					return true;
-				}
+		const auto start = std::chrono::steady_clock::now();
+		const auto deadline = start + std::chrono::seconds(timeout);
+		while (!(queue.size() == 0 && state.load(std::memory_order_acquire) == Retrieving)) {
+			if constexpr (emper::IO) {
+				emper::sleep(1);
+			} else {
+				emper::yield();
 			}
-
-			return false;
-		} else {
-			const auto start = std::chrono::steady_clock::now();
-			const auto deadline = start + std::chrono::seconds(timeout);
-			while (!(queue.size() == 0 && state.load(std::memory_order_acquire) == Retrieving)) {
-				// TODO: The suppressed linter error below may be a false positive
-				// reported by clang-tidy.
-				// NOLINTNEXTLINE(modernize-use-nullptr)
-				if (std::chrono::steady_clock::now() > deadline) {
-					return false;
-				}
+			// TODO: The suppressed linter error below may be a false positive
+			// reported by clang-tidy.
+			// NOLINTNEXTLINE(modernize-use-nullptr)
+			if (std::chrono::steady_clock::now() > deadline) {
+				return false;
 			}
-
-			return true;
 		}
+
+		return true;
 	}
 };
diff --git a/emper/Runtime.cpp b/emper/Runtime.cpp
index e195d3e05f24a07876511c1beb6002b2cc56fc54..17ef6b9b0982a8d6921439403872736090a32f6c 100644
--- a/emper/Runtime.cpp
+++ b/emper/Runtime.cpp
@@ -14,7 +14,8 @@
 #include <memory>	 // for __shared_ptr_access, shared_ptr
 #include <string>	 // for string
 
-#include "Common.hpp"					 // for DIE_MSG_ERRNO, DIE, DIE_MSG
+#include "Common.hpp"	 // for DIE_MSG_ERRNO, DIE, DIE_MSG
+#include "Context.hpp"
 #include "ContextManager.hpp"	 // for ContextManager
 #include "Debug.hpp"					 // for DBG, ABORT, LOGD, LOGE
 #include "Emper.hpp"
@@ -209,6 +210,15 @@ auto Runtime::workerLoop(Worker* worker) -> void* {
 	return nullptr;
 }
 
+void Runtime::yield() {
+	Context* context = Context::getCurrentContext();
+	contextManager.saveAndStartNew([context, this] {
+		Fiber* resumeFiber =
+				Fiber::from([context, this] { this->contextManager.discardAndResume(context); });
+		this->scheduleFromAnywhere(*resumeFiber);
+	});
+}
+
 auto Runtime::nextFiber() -> NextFiberResult {
 	if constexpr (emper::IO) {
 		// Schedule all fibers waiting on completed IO
diff --git a/emper/Runtime.hpp b/emper/Runtime.hpp
index 803d935d38fb15e7a8a58ec24b8142985781373e..92487a336c3219860a1dafce3182c955df6113cd 100644
--- a/emper/Runtime.hpp
+++ b/emper/Runtime.hpp
@@ -153,6 +153,8 @@ class Runtime : public Logger<LogSubsystem::RUNTI> {
 
 	inline void scheduleFromAnywhere(Fiber& fiber) { scheduler.scheduleFromAnywhere(fiber); }
 
+	void yield();
+
 	// TODO: This should probably not be a public method of Runtime.
 	auto nextFiber() -> NextFiberResult;
 
diff --git a/emper/include/emper.hpp b/emper/include/emper.hpp
index 82e1f259a784354baa923ddd31a3cb65887bb88b..c5691e232354e9803ab2c7e978de0f503b1c74f5 100644
--- a/emper/include/emper.hpp
+++ b/emper/include/emper.hpp
@@ -6,9 +6,11 @@
 #include <functional>
 #include <utility>
 
+#include "Common.hpp"
 #include "Fiber.hpp"
 #include "Runtime.hpp"
 #include "SynchronizedFiber.hpp"
+#include "io/Future.hpp"
 
 void async(Fiber* fiber) {
 	assert(fiber != nullptr);
@@ -59,3 +61,22 @@ void spawn(Fiber::fiber_fun0_t function, workeraffinity_t* affinity, S& semaphor
 	Fiber* fiber = SynchronizedFiber::from(function, affinity, semaphore);
 	async(fiber);
 }
+
+namespace emper {
+void yield() {
+	Runtime* runtime = Runtime::getRuntime();
+	runtime->yield();
+}
+
+auto sleep(unsigned int seconds) -> bool {
+	if constexpr (!emper::IO) {
+		DIE_MSG("sleep requires emper::io");
+	}
+
+	emper::io::AlarmFuture::Timespec ts = {.tv_sec = seconds, .tv_nsec = 0};
+	emper::io::AlarmFuture alarm(ts);
+	int32_t res = alarm.submitAndWait();
+
+	return res == -ETIME;
+}
+}	 // namespace emper
diff --git a/tests/YieldToAnywhereTest.cpp b/tests/YieldToAnywhereTest.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ee0fb8ef89552e30b4119c41e0017e25494e0a47
--- /dev/null
+++ b/tests/YieldToAnywhereTest.cpp
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright © 2020 Florian Schmaus
+#include <cstdlib>
+
+#include "CountingPrivateSemaphore.hpp"
+#include "emper.hpp"
+
+void emperTest() {
+	const unsigned fiberCount = 100;
+	CPS cps;
+	for (unsigned i = 0; i < fiberCount; ++i) {
+		spawn(
+				[] {
+					for (unsigned i = 0; i < 100; ++i) {
+						emper::yield();
+					}
+				},
+				cps);
+	}
+	cps.wait();
+	exit(EXIT_SUCCESS);
+}
diff --git a/tests/meson.build b/tests/meson.build
index 89518c6bf6099a3d023c4434af28831f333fbb22..52662f38a5bcdb2ce515401a27afcbf3f56911c0 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -29,6 +29,12 @@ tests = {
 			'description': 'Test EMPER\'s C++ API',
 		  },
 
+		  'YieldToAnywhereTest.cpp':
+		  {
+			'description': 'Test emper::yieldToAnywhere',
+			'test_runner': 'emper',
+		  },
+
 		  'ReuseBpsTest.cpp':
 		  {
 			'description': 'Test resetting of BPSs',