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.