diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/ProfileManagerWidget.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/ProfileManagerWidget.cs
index df2ec5ce6..8ec6be867 100644
--- a/Dalamud/Interface/Internal/Windows/PluginInstaller/ProfileManagerWidget.cs
+++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/ProfileManagerWidget.cs
@@ -111,6 +111,7 @@ internal class ProfileManagerWidget
{
Guid? toCloneGuid = null;
+ using var syncScope = profman.GetSyncScope();
foreach (var profile in profman.Profiles)
{
if (profile.IsDefaultProfile)
@@ -329,7 +330,8 @@ internal class ProfileManagerWidget
var pluginLineHeight = 32 * ImGuiHelpers.GlobalScale;
string? wantRemovePluginInternalName = null;
- foreach (var plugin in profile.Plugins)
+ using var syncScope = profile.GetSyncScope();
+ foreach (var plugin in profile.Plugins.ToArray())
{
didAny = true;
var pmPlugin = pm.InstalledPlugins.FirstOrDefault(x => x.Manifest.InternalName == plugin.InternalName);
diff --git a/Dalamud/Plugin/Internal/Profiles/Profile.cs b/Dalamud/Plugin/Internal/Profiles/Profile.cs
index 29ab64fa7..71feff0c2 100644
--- a/Dalamud/Plugin/Internal/Profiles/Profile.cs
+++ b/Dalamud/Plugin/Internal/Profiles/Profile.cs
@@ -109,6 +109,13 @@ internal class Profile
///
public ProfileModel Model => this.modelV1;
+ ///
+ /// Get a disposable that will lock the plugin list while it is not disposed.
+ /// You must NEVER use this in async code.
+ ///
+ /// The aforementioned disposable.
+ public IDisposable GetSyncScope() => new ScopedSyncRoot(this);
+
///
/// Set this profile's state. This cannot be called for the default profile.
/// This will block until all states have been applied.
diff --git a/Dalamud/Plugin/Internal/Profiles/ProfileManager.cs b/Dalamud/Plugin/Internal/Profiles/ProfileManager.cs
index a3601c773..46b572c1a 100644
--- a/Dalamud/Plugin/Internal/Profiles/ProfileManager.cs
+++ b/Dalamud/Plugin/Internal/Profiles/ProfileManager.cs
@@ -58,6 +58,13 @@ internal class ProfileManager : IServiceType
/// Gets a value indicating whether or not the profile manager is busy enabling/disabling plugins.
///
public bool IsBusy => this.isBusy;
+
+ ///
+ /// Get a disposable that will lock the profile list while it is not disposed.
+ /// You must NEVER use this in async code.
+ ///
+ /// The aforementioned disposable.
+ public IDisposable GetSyncScope() => new ScopedSyncRoot(this.profiles);
///
/// Check if any enabled profile wants a specific plugin enabled.