Unity线程切换问题:确保被动搜索线程终止后启动主动搜索
我正在开发一款跳棋AI,AI回合时通过_activeSearchThread线程启动ActiveSearch寻找最优走法;对手回合时通过_passiveSearchThread线程启动PassiveSearch,用于预测对手走法并搜索应对策略。
核心问题是AI类中存在一个被两个线程共用的置换表(transpositions列表)。当对手走完棋后,尝试中断被动搜索线程并启动主动搜索,但Thread.Interrupt无法即时生效——仅当线程处于WaitSleepJoin状态时才会触发中断。这会导致主动搜索已启动,被动搜索却仍在运行,两者同时操作置换表引发如下报错:
InvalidOperationException: Collection was modified; enumeration operation may not execute.
调试时发现,调用_passiveSearchThread?.Interrupt()后立即打印_passiveSearchThread.IsAlive,输出为TRUE,说明被动搜索线程尚未真正终止。需要确保只有在被动搜索线程完全中断后,才启动主动搜索线程。
相关代码
public void PerformMove (Move move) // Called by the ai when it has made a decision { OnAiMadeMove?.Invoke(move); // Raises an event that the game manager is subscribed to } public void ReceiveOpponentsMove (Move move) // Called by the game manager to notify the ai that the opponent has made move { _passiveSearchThread?.Interrupt(); // Interrupting passive search if (_passiveSearchThread != null) print(_passiveSearchThread.IsAlive); // *** (see comments below) _ai.ReceiveOpponentsMove(move); // Send some data of the opponent's move to the ai StartCoroutine(StartActiveSearch()); // Start active search coroutine } private IEnumerator StartActiveSearch () { _ai.PrepareForActiveSearch(_manager.GetCurrentTime(_isWhite)); // Some pre-settings _activeSearchThread = new Thread(_ai.FindBestMove); // An active search thread _activeSearchThread.Name = "ActiveThread"; _activeSearchThread.Start(); // Starting it (problem is here: it is not guaranteed that the passive search is off at this moment) yield return new WaitWhile(() => _activeSearchThread.IsAlive); // Waiting the ai to make a decision // Accepting the ai's decision and sending it to the game manager Move move = _ai.GetBestMove(); PerformMove(move); _ai.MoveAccepted(move); StartPassiveSearch(); } private void StartPassiveSearch () { _passiveSearchThread = new Thread(_ai.PassiveSearch); // A passive search thread _passiveSearchThread.Name = "PassiveThread"; _passiveSearchThread.Start(); // Starting it (when ai has made move and it is the opponent's turn) }
*** 调试显示上述代码中print输出TRUE,说明被动搜索线程尚未被中断。
解决方案
1. 替换Thread.Interrupt为自定义终止标志
Thread.Interrupt依赖线程进入等待状态才能触发,可靠性不足。建议在AI类中添加线程安全的终止标志,让被动搜索线程主动检查并退出:
public class AI { private readonly object _lockObj = new object(); private bool _stopPassiveSearch; public void StopPassiveSearch() { lock(_lockObj) { _stopPassiveSearch = true; } } public void PassiveSearch() { while(true) { // 每次循环开始前检查终止标志 lock(_lockObj) { if(_stopPassiveSearch) { _stopPassiveSearch = false; // 重置标志,以备下次启动 break; } } // 执行被动搜索逻辑 // ... // 可选:添加短暂睡眠,减少CPU占用,同时让标志检查更及时 Thread.Sleep(10); } } }
2. 等待被动搜索线程完全终止后再启动主动搜索
修改ReceiveOpponentsMove方法,调用终止标志后,等待被动线程结束:
public void ReceiveOpponentsMove (Move move) { if (_passiveSearchThread != null && _passiveSearchThread.IsAlive) { _ai.StopPassiveSearch(); _passiveSearchThread.Join(); // 阻塞当前线程,直到被动搜索线程终止 _passiveSearchThread = null; // 清空引用 } _ai.ReceiveOpponentsMove(move); StartCoroutine(StartActiveSearch()); }
3. 为置换表添加线程安全保护(额外保障)
即使确保了线程串行执行,也可以为共享的置换表添加线程锁,防止意外的并发访问:
public class AI { private readonly List<Transposition> _transpositions = new List<Transposition>(); private readonly object _transpositionLock = new object(); public void AddTransposition(Transposition t) { lock(_transpositionLock) { _transpositions.Add(t); } } public Transposition GetTransposition(ulong key) { lock(_transpositionLock) { return _transpositions.FirstOrDefault(t => t.Key == key); } } // 其他操作置换表的方法都需要加锁 }
4. 优化线程管理:使用CancellationToken替代自定义标志(推荐)
如果使用.NET Framework 4.0+,可以使用CancellationToken更优雅地管理线程终止:
public class AI { private CancellationTokenSource _passiveCts; public void StopPassiveSearch() { _passiveCts?.Cancel(); } public void PassiveSearch() { _passiveCts = new CancellationTokenSource(); var token = _passiveCts.Token; while(!token.IsCancellationRequested) { // 执行被动搜索逻辑 // ... // 可选:使用ThrowIfCancellationRequested快速终止 // token.ThrowIfCancellationRequested(); Thread.Sleep(10); } } }
内容的提问来源于stack exchange,提问作者Максим Фисман

