You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity线程切换问题:确保被动搜索线程终止后启动主动搜索

跳棋AI线程冲突问题解决

我正在开发一款跳棋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,提问作者Максим Фисман

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 23:24:56