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

使用Thread仍遇While循环阻塞主线程问题求助(Godot引擎)

问题分析与解决方案

核心问题

你的代码存在三个致命问题,导致While循环阻塞主线程:

  1. Thread使用完全错误:stone_thread.start(await disable(...))会先在主线程执行disable函数(await强制主线程等待协程完成),再把空返回值传给Thread.start——线程根本没执行你的逻辑,所有代码全程在主线程跑,死循环自然阻塞主线程。
  2. 线程操作节点违规:Godot的节点、场景树API都是线程不安全的,自定义Thread里调用get_overlapping_areas()、修改sprite.modulate或body.colbox属性,会导致崩溃或未定义行为。
  3. 死循环忙等:即使线程能跑,while !(player in get_overlapping_areas()): pass是无休眠的忙等,会占满CPU核心,且线程无法正确获取主线程维护的区域重叠状态。

正确解决方案(优先用协程,无需Thread)

你的逻辑全是依赖节点操作和等待事件,用Godot原生协程(await)就能实现非阻塞效果,完全不需要Thread:

extends Area2D

@onready var sprite = $Sprite
@onready var audio = $Audio
@onready var body = $Body
var rock = false

func disable_stone(player):
    if rock:
        return
    # 直接启动协程,自动在主线程非阻塞调度
    await disable(player)

func disable(player):
    rock = true
    print("super")
    
    await get_tree().create_timer(0.5).timeout
    body.colbox.disabled = true
    sprite.modulate.a8 = 100
    audio.play()
    
    await get_tree().create_timer(2).timeout
    # 用信号+await代替死循环,非阻塞等待玩家进入区域
    await wait_for_player_overlap(player)
    
    body.colbox.disabled = false
    sprite.modulate.a8 = 255
    rock = false

func wait_for_player_overlap(player):
    # 先检查是否已经重叠,避免不必要等待
    if player in get_overlapping_areas():
        return
    
    # 监听区域进入信号,满足条件时结束等待
    var conn = connect("area_entered", func(area):
        if area == player:
            disconnect(conn)
            get_tree().quit_coroutine(get_current_coroutine())
    )
    # 挂起协程直到信号触发
    await get_tree().idle_frame

为什么这个方案有效?

  • 协程是主线程内的非阻塞调度,await会让当前逻辑挂起,主线程可以继续处理其他任务,不会被阻塞。
  • 用area_entered信号代替死循环,完全避免CPU空转,且符合Godot的事件驱动设计。
  • 所有节点操作都在主线程执行,完全符合线程安全要求。

若一定要用Thread(不推荐,仅作示例)

如果你的场景确实需要线程处理计算密集型任务,必须通过线程安全的方式和主线程交互,所有节点操作必须回到主线程执行:

extends Area2D

@onready var sprite = $Sprite
@onready var audio = $Audio
@onready var body = $Body
var rock = false
var thread: Thread
var player_path: NodePath
var is_player_overlapping = false
var mutex = Mutex.new()

func _ready():
    thread = Thread.new()

func disable_stone(player):
    if rock:
        return
    rock = true
    player_path = player.get_path()
    thread.start(_thread_logic)

func _thread_logic():
    print("super")
    # 线程内用OS延迟代替定时器(不精确,仅作示例)
    OS.delay_msec(500)
    # 委托主线程修改节点状态
    call_deferred("_set_stone_disabled", true)
    
    OS.delay_msec(2000)
    # 线程内循环等待主线程传递的重叠状态
    while true:
        mutex.lock()
        var check_result = is_player_overlapping
        mutex.unlock()
        if check_result:
            break
        OS.delay_msec(16) # 模拟60帧间隔,避免CPU空转
    
    call_deferred("_set_stone_disabled", false)
    mutex.lock()
    rock = false
    mutex.unlock()
    thread.wait_to_finish()

func _set_stone_disabled(is_disabled: bool):
    body.colbox.disabled = is_disabled
    sprite.modulate.a8 = is_disabled ? 100 : 255
    if is_disabled:
        audio.play()

func _process(_delta):
    # 主线程负责检测区域重叠,线程安全更新状态
    if rock:
        var player = get_node_or_null(player_path)
        mutex.lock()
        is_player_overlapping = player != null && player in get_overlapping_areas()
        mutex.unlock()

func _exit_tree():
    # 退出前终止线程,避免内存泄漏
    if thread.is_active():
        thread.terminate()

内容的提问来源于stack exchange,提问作者SuperViktor123 Hah

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 10:25:21