From ccc3d046ca68661c051d286a05e95e8ab470b709 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Mon, 2 Mar 2026 21:33:00 +0800 Subject: [PATCH] =?UTF-8?q?fix(pause):=20=E4=BF=AE=E5=A4=8D=E6=9A=82?= =?UTF-8?q?=E5=81=9C=E7=AE=A1=E7=90=86=E5=99=A8=E7=9A=84=E5=B9=B6=E5=8F=91?= =?UTF-8?q?=E5=AE=89=E5=85=A8=E5=92=8C=E8=B5=84=E6=BA=90=E6=B8=85=E7=90=86?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加了_disposed标志位用于跟踪对象销毁状态 - 实现了完整的DestroyAsync方法进行资源清理和数据结构清空 - 添加了ThrowIfDisposed方法防止对象销毁后被使用 - 将状态变更通知移到锁外部以避免死锁问题 - 在多个公共方法中添加了销毁状态检查 - 修复了ClearGroup和ClearAll方法中的死锁风险 - 改进了异常处理机制,确保线程安全 --- GFramework.Core/pause/PauseStackManager.cs | 126 +++++++++++++++++---- 1 file changed, 105 insertions(+), 21 deletions(-) diff --git a/GFramework.Core/pause/PauseStackManager.cs b/GFramework.Core/pause/PauseStackManager.cs index e561bdf..ff2d5b8 100644 --- a/GFramework.Core/pause/PauseStackManager.cs +++ b/GFramework.Core/pause/PauseStackManager.cs @@ -17,6 +17,7 @@ public class PauseStackManager : AbstractContextUtility, IPauseStackManager, IAs private readonly ILogger _logger = LoggerFactoryResolver.Provider.CreateLogger(nameof(PauseStackManager)); private readonly Dictionary> _pauseStacks = new(); private readonly Dictionary _tokenMap = new(); + private volatile bool _disposed; /// /// 异步销毁方法,在组件销毁时调用。 @@ -24,7 +25,32 @@ public class PauseStackManager : AbstractContextUtility, IPauseStackManager, IAs /// 表示异步操作完成的任务。 public ValueTask DestroyAsync() { + if (_disposed) + return ValueTask.CompletedTask; + + _lock.EnterWriteLock(); + try + { + if (_disposed) + return ValueTask.CompletedTask; + + _disposed = true; + + // 清理所有数据结构 + _pauseStacks.Clear(); + _tokenMap.Clear(); + _handlers.Clear(); + + _logger.Debug("PauseStackManager destroyed"); + } + finally + { + _lock.ExitWriteLock(); + } + + // 释放锁资源 _lock.Dispose(); + return ValueTask.CompletedTask; } @@ -41,9 +67,14 @@ public class PauseStackManager : AbstractContextUtility, IPauseStackManager, IAs /// 表示此次暂停请求的令牌。 public PauseToken Push(string reason, PauseGroup group = PauseGroup.Global) { + PauseToken token; + bool shouldNotify = false; + _lock.EnterWriteLock(); try { + ThrowIfDisposed(); + var wasPaused = IsPausedInternal(group); var entry = new PauseEntry @@ -65,18 +96,23 @@ public class PauseStackManager : AbstractContextUtility, IPauseStackManager, IAs _logger.Debug($"Pause pushed: {reason} (Group: {group}, Depth: {stack.Count})"); - // 状态变化检测:从未暂停 → 暂停 - if (!wasPaused) - { - NotifyHandlers(group, true); - } + token = new PauseToken(entry.TokenId); - return new PauseToken(entry.TokenId); + // 状态变化检测:从未暂停 → 暂停 + shouldNotify = !wasPaused; } finally { _lock.ExitWriteLock(); } + + // 在锁外通知处理器,避免死锁 + if (shouldNotify) + { + NotifyHandlers(group, true); + } + + return token; } /// @@ -89,9 +125,15 @@ public class PauseStackManager : AbstractContextUtility, IPauseStackManager, IAs if (!token.IsValid) return false; + bool found; + bool shouldNotify = false; + PauseGroup notifyGroup = PauseGroup.Global; + _lock.EnterWriteLock(); try { + ThrowIfDisposed(); + if (!_tokenMap.TryGetValue(token.Id, out var entry)) { _logger.Warn($"Attempted to pop invalid/expired token: {token.Id}"); @@ -104,7 +146,7 @@ public class PauseStackManager : AbstractContextUtility, IPauseStackManager, IAs // 从栈中移除 var tempStack = new Stack(); - bool found = false; + found = false; while (stack.Count > 0) { @@ -132,16 +174,23 @@ public class PauseStackManager : AbstractContextUtility, IPauseStackManager, IAs // 状态变化检测:从暂停 → 未暂停 if (wasPaused && stack.Count == 0) { - NotifyHandlers(group, false); + shouldNotify = true; + notifyGroup = group; } } - - return found; } finally { _lock.ExitWriteLock(); } + + // 在锁外通知处理器,避免死锁 + if (shouldNotify) + { + NotifyHandlers(notifyGroup, false); + } + + return found; } /// @@ -154,6 +203,7 @@ public class PauseStackManager : AbstractContextUtility, IPauseStackManager, IAs _lock.EnterReadLock(); try { + ThrowIfDisposed(); return IsPausedInternal(group); } finally @@ -172,6 +222,7 @@ public class PauseStackManager : AbstractContextUtility, IPauseStackManager, IAs _lock.EnterReadLock(); try { + ThrowIfDisposed(); return _pauseStacks.TryGetValue(group, out var stack) ? stack.Count : 0; } finally @@ -190,6 +241,8 @@ public class PauseStackManager : AbstractContextUtility, IPauseStackManager, IAs _lock.EnterReadLock(); try { + ThrowIfDisposed(); + if (!_pauseStacks.TryGetValue(group, out var stack)) return Array.Empty(); @@ -209,6 +262,10 @@ public class PauseStackManager : AbstractContextUtility, IPauseStackManager, IAs /// 表示暂停作用域的 IDisposable 对象。 public IDisposable PauseScope(string reason, PauseGroup group = PauseGroup.Global) { + if (_disposed) + throw new ObjectDisposedException(nameof(PauseStackManager), + "Cannot use PauseStackManager after it has been destroyed"); + return new PauseScope(this, reason, group); } @@ -218,9 +275,13 @@ public class PauseStackManager : AbstractContextUtility, IPauseStackManager, IAs /// 要清空的暂停组。 public void ClearGroup(PauseGroup group) { + bool shouldNotify = false; + _lock.EnterWriteLock(); try { + ThrowIfDisposed(); + if (!_pauseStacks.TryGetValue(group, out var stack)) return; @@ -237,15 +298,18 @@ public class PauseStackManager : AbstractContextUtility, IPauseStackManager, IAs _logger.Warn($"Cleared all pauses for group: {group}"); // 状态变化检测 - if (wasPaused) - { - NotifyHandlers(group, false); - } + shouldNotify = wasPaused; } finally { _lock.ExitWriteLock(); } + + // 在锁外通知处理器,避免死锁 + if (shouldNotify) + { + NotifyHandlers(group, false); + } } /// @@ -253,10 +317,14 @@ public class PauseStackManager : AbstractContextUtility, IPauseStackManager, IAs /// public void ClearAll() { + List pausedGroups; + _lock.EnterWriteLock(); try { - var pausedGroups = _pauseStacks + ThrowIfDisposed(); + + pausedGroups = _pauseStacks .Where(kvp => kvp.Value.Count > 0) .Select(kvp => kvp.Key) .ToList(); @@ -265,17 +333,17 @@ public class PauseStackManager : AbstractContextUtility, IPauseStackManager, IAs _tokenMap.Clear(); _logger.Warn("Cleared all pauses for all groups"); - - // 通知所有之前暂停的组 - foreach (var group in pausedGroups) - { - NotifyHandlers(group, false); - } } finally { _lock.ExitWriteLock(); } + + // 在锁外通知所有之前暂停的组,避免死锁 + foreach (var group in pausedGroups) + { + NotifyHandlers(group, false); + } } /// @@ -287,6 +355,8 @@ public class PauseStackManager : AbstractContextUtility, IPauseStackManager, IAs _lock.EnterWriteLock(); try { + ThrowIfDisposed(); + if (!_handlers.Contains(handler)) { _handlers.Add(handler); @@ -308,6 +378,8 @@ public class PauseStackManager : AbstractContextUtility, IPauseStackManager, IAs _lock.EnterWriteLock(); try { + ThrowIfDisposed(); + if (_handlers.Remove(handler)) { _logger.Debug($"Unregistered pause handler: {handler.GetType().Name}"); @@ -319,6 +391,18 @@ public class PauseStackManager : AbstractContextUtility, IPauseStackManager, IAs } } + /// + /// 检查是否已销毁,如果已销毁则抛出异常 + /// + private void ThrowIfDisposed() + { + if (_disposed) + { + throw new ObjectDisposedException(nameof(PauseStackManager), + "Cannot use PauseStackManager after it has been destroyed"); + } + } + /// /// 内部查询暂停状态的方法,不加锁。 ///