使用Thread仍遇While循环阻塞主线程问题求助(Godot引擎)
问题分析与解决方案
核心问题
你的代码存在三个致命问题,导致While循环阻塞主线程:
- Thread使用完全错误:
stone_thread.start(await disable(...))会先在主线程执行disable函数(await强制主线程等待协程完成),再把空返回值传给Thread.start——线程根本没执行你的逻辑,所有代码全程在主线程跑,死循环自然阻塞主线程。 - 线程操作节点违规:Godot的节点、场景树API都是线程不安全的,自定义Thread里调用
get_overlapping_areas()、修改sprite.modulate或body.colbox属性,会导致崩溃或未定义行为。 - 死循环忙等:即使线程能跑,
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
相关产品推荐
相关产品推荐

