diff --git a/src/core/core.cpp b/src/core/core.cpp
index 0d7b58e2c..f1d5c7b00 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -629,6 +629,7 @@ void System::serialize(Archive& ar, const unsigned int file_version) {
 
     // This needs to be set from somewhere - might as well be here!
     if (Archive::is_loading::value) {
+        timing->UnlockEventQueue();
         Service::GSP::SetGlobalModule(*this);
         memory->SetDSP(*dsp_core);
         cheat_engine->Connect();
diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp
index 3d77932b6..4aa6d870e 100644
--- a/src/core/core_timing.cpp
+++ b/src/core/core_timing.cpp
@@ -49,6 +49,10 @@ TimingEventType* Timing::RegisterEvent(const std::string& name, TimedCallback ca
 
 void Timing::ScheduleEvent(s64 cycles_into_future, const TimingEventType* event_type, u64 userdata,
                            std::size_t core_id) {
+    if (event_queue_locked) {
+        return;
+    }
+
     ASSERT(event_type != nullptr);
     Timing::Timer* timer = nullptr;
     if (core_id == std::numeric_limits<std::size_t>::max()) {
@@ -74,6 +78,9 @@ void Timing::ScheduleEvent(s64 cycles_into_future, const TimingEventType* event_
 }
 
 void Timing::UnscheduleEvent(const TimingEventType* event_type, u64 userdata) {
+    if (event_queue_locked) {
+        return;
+    }
     for (auto timer : timers) {
         auto itr = std::remove_if(
             timer->event_queue.begin(), timer->event_queue.end(),
@@ -89,6 +96,9 @@ void Timing::UnscheduleEvent(const TimingEventType* event_type, u64 userdata) {
 }
 
 void Timing::RemoveEvent(const TimingEventType* event_type) {
+    if (event_queue_locked) {
+        return;
+    }
     for (auto timer : timers) {
         auto itr = std::remove_if(timer->event_queue.begin(), timer->event_queue.end(),
                                   [&](const Event& e) { return e.type == event_type; });
diff --git a/src/core/core_timing.h b/src/core/core_timing.h
index aebe5c742..611122211 100644
--- a/src/core/core_timing.h
+++ b/src/core/core_timing.h
@@ -280,6 +280,11 @@ public:
 
     std::shared_ptr<Timer> GetTimer(std::size_t cpu_id);
 
+    // Used after deserializing to unprotect the event queue.
+    void UnlockEventQueue() {
+        event_queue_locked = false;
+    }
+
 private:
     // unordered_map stores each element separately as a linked list node so pointers to
     // elements remain stable regardless of rehashes/resizing.
@@ -292,6 +297,10 @@ private:
     // under/overclocking the guest cpu
     double cpu_clock_scale = 1.0;
 
+    // When true, the event queue can't be modified. Used while deserializing to workaround
+    // destructor side effects.
+    bool event_queue_locked = false;
+
     template <class Archive>
     void serialize(Archive& ar, const unsigned int file_version) {
         // event_types set during initialization of other things
@@ -303,6 +312,9 @@ private:
         } else {
             ar& current_timer;
         }
+        if (Archive::is_loading::value) {
+            event_queue_locked = true;
+        }
     }
     friend class boost::serialization::access;
 };