diff --git a/event_loop.cpp b/event_loop.cpp
new file mode 100644
index 0000000..7b6ea65
--- /dev/null
+++ b/event_loop.cpp
@@ -0,0 +1,99 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2020, Google Inc.
+ *
+ * event_loop.cpp - Event loop based on cam
+ */
+
+#include "event_loop.h"
+
+#include <assert.h>
+#include <event2/event.h>
+#include <event2/thread.h>
+
+EventLoop *EventLoop::instance_ = nullptr;
+
+EventLoop::EventLoop()
+{
+	assert(!instance_);
+
+	evthread_use_pthreads();
+	event_ = event_base_new();
+	instance_ = this;
+}
+
+EventLoop::~EventLoop()
+{
+	instance_ = nullptr;
+
+	event_base_free(event_);
+	libevent_global_shutdown();
+}
+
+int EventLoop::exec()
+{
+	exitCode_ = -1;
+	exit_.store(false, std::memory_order_release);
+
+	while (!exit_.load(std::memory_order_acquire)) {
+		dispatchCalls();
+		event_base_loop(event_, EVLOOP_NO_EXIT_ON_EMPTY);
+	}
+
+	return exitCode_;
+}
+
+void EventLoop::exit(int code)
+{
+	exitCode_ = code;
+	exit_.store(true, std::memory_order_release);
+	interrupt();
+}
+
+void EventLoop::interrupt()
+{
+	event_base_loopbreak(event_);
+}
+
+
+void EventLoop::timeoutTriggered(int fd, short event, void *arg)
+{
+	EventLoop *self = static_cast<EventLoop *>(arg);
+	self->exit();
+}
+
+void EventLoop::timeout(unsigned int sec)
+{
+	struct event *ev;
+	struct timeval tv;
+
+	tv.tv_sec = sec;
+	tv.tv_usec = 0;
+	ev = evtimer_new(event_, &timeoutTriggered, this);
+	evtimer_add(ev, &tv);
+}
+
+void EventLoop::callLater(const std::function<void()> &func)
+{
+	{
+		std::unique_lock<std::mutex> locker(lock_);
+		calls_.push_back(func);
+	}
+
+	interrupt();
+}
+
+void EventLoop::dispatchCalls()
+{
+	std::unique_lock<std::mutex> locker(lock_);
+
+	for (auto iter = calls_.begin(); iter != calls_.end(); ) {
+		std::function<void()> call = std::move(*iter);
+
+		iter = calls_.erase(iter);
+
+		locker.unlock();
+		call();
+		locker.lock();
+	}
+}
diff --git a/event_loop.h b/event_loop.h
new file mode 100644
index 0000000..003c3d8
--- /dev/null
+++ b/event_loop.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2020, Google Inc.
+ *
+ * event_loop.h - Event loop based on cam's
+ */
+#ifndef __SIMPLE_CAM_EVENT_LOOP_H__
+#define __SIMPLE_CAM_EVENT_LOOP_H__
+
+#include <atomic>
+#include <functional>
+#include <list>
+#include <mutex>
+
+struct event_base;
+
+class EventLoop
+{
+public:
+	EventLoop();
+	~EventLoop();
+
+	void exit(int code = 0);
+	int exec();
+
+	void timeout(unsigned int sec);
+	void callLater(const std::function<void()> &func);
+
+private:
+	static EventLoop *instance_;
+
+	static void timeoutTriggered(int fd, short event, void *arg);
+
+	struct event_base *event_;
+	std::atomic<bool> exit_;
+	int exitCode_;
+
+	std::list<std::function<void()>> calls_;
+	std::mutex lock_;
+
+	void interrupt();
+	void dispatchCalls();
+};
+
+#endif /* __SIMPLE_CAM_EVENT_LOOP_H__ */
diff --git a/meson.build b/meson.build
index c312f2c..4d580c2 100644
--- a/meson.build
+++ b/meson.build
@@ -8,12 +8,14 @@ project('simple-cam', 'c', 'cpp',
 # simple-cam.cpp is the fully commented application
 src_files = files([
 	'simple-cam.cpp',
+	'event_loop.cpp',
 ])
 
 # Point your PKG_CONFIG_PATH environment variable to the
 # libcamera install path camera.pc file ($prefix/lib/pkgconfig/camera.pc)
 libcamera_deps = [
       dependency('camera', required : true),
+      dependency('libevent_pthreads'),
 ]
 
 cpp_arguments = [ '-Wno-unused-parameter', ]
diff --git a/simple-cam.cpp b/simple-cam.cpp
index bfe30d7..6d1d84f 100644
--- a/simple-cam.cpp
+++ b/simple-cam.cpp
@@ -11,8 +11,13 @@
 
 #include <libcamera/libcamera.h>
 
+#include "event_loop.h"
+
+#define TIMEOUT_SEC 3
+
 using namespace libcamera;
 std::shared_ptr<Camera> camera;
+EventLoop loop;
 
 /*
  * --------------------------------------------------------------------
@@ -21,13 +26,26 @@ std::shared_ptr<Camera> camera;
  * For each Camera::requestCompleted Signal emitted from the Camera the
  * connected Slot is invoked.
  *
+ * The Slot is invoked in the CameraManager's thread, hence one should avoid
+ * any heavy processing here. The processing of the request shall be re-directed
+ * to the application's thread instead, so as not to block the CameraManager's
+ * thread for large amount of time.
+ *
  * The Slot receives the Request as a parameter.
  */
+
+static void processRequest(Request *request);
+
 static void requestComplete(Request *request)
 {
 	if (request->status() == Request::RequestCancelled)
 		return;
 
+	loop.callLater(std::bind(&processRequest, request));
+}
+
+static void processRequest(Request *request)
+{
 	const Request::BufferMap &buffers = request->buffers();
 
 	for (auto bufferPair : buffers) {
@@ -320,20 +338,11 @@ int main()
 	 *
 	 * In order to dispatch events received from the video devices, such
 	 * as buffer completions, an event loop has to be run.
-	 *
-	 * Libcamera provides its own default event dispatcher realized by
-	 * polling a set of file descriptors, but applications can integrate
-	 * their own even loop with the Libcamera EventDispatcher.
-	 *
-	 * Here, as an example, run the poll-based EventDispatcher for 3
-	 * seconds.
 	 */
-	EventDispatcher *dispatcher = cm->eventDispatcher();
-	Timer timer;
-	timer.start(3000);
-	while (timer.isRunning())
-		dispatcher->processEvents();
-
+	loop.timeout(TIMEOUT_SEC);
+	int ret = loop.exec();
+	std::cout << "Capture ran for " << TIMEOUT_SEC << " seconds and "
+		  << "stopped with exit status: " << ret << std::endl;
 	/*
 	 * --------------------------------------------------------------------
 	 * Clean Up
