如何使用UniTaskVoid订阅Unity事件?
解决UniTask在Unity UI事件订阅中替代async void的闭包问题
问题背景
UniTask文档明确建议避免使用async void,转而使用UniTaskVoid处理异步逻辑,但在订阅Unity UI事件(比如Button的onClick)时,直接用UniTaskVoid会出现执行时机错误,可行方案又会触发Rider的"可消除闭包"警告。
错误尝试分析
直接传入
UniTaskVoid方法:_restartGameButton.onClick.AddListener(RestartGame);
Unity的AddListener需要UnityAction(无参无返回值委托),但UniTaskVoid方法返回值为UniTaskVoid,无法直接适配,编译会报错。错误调用
RestartGame().Forget:_restartGameButton.onClick.AddListener(RestartGame().Forget);
这种写法会立即执行RestartGame()方法,而非将其作为事件回调注册,完全不符合预期。
消除闭包的正确实现
核心是避免用lambda表达式捕获上下文对象,将异步逻辑抽成独立的UniTaskVoid成员方法,再通过UniTask.Void的方法组重载适配UnityAction:
public class WindowGamePause : WindowView { [SerializeField] private Button _restartGameButton; // 假设已通过其他方式初始化_gameManager private GameManager _gameManager; private void Awake() { _restartGameButton.onClick.AddListener(OnRestartButtonClicked); } // 事件回调的同步方法,无闭包 private void OnRestartButtonClicked() { // 直接传入异步方法的方法组,避免创建闭包 UniTask.Void(RestartGameAsync); } // 独立的异步逻辑方法 private async UniTaskVoid RestartGameAsync() { await Close(); _gameManager.RestartGame(); } }
原理说明
- 之前的lambda写法
async () => { ... }会捕获当前类实例(this)和_gameManager变量,创建额外闭包对象,触发Rider警告。 - 使用方法组
RestartGameAsync传入UniTask.Void时,直接引用成员方法地址,无需捕获上下文,不会产生闭包,警告自然消除。 UniTask.Void的作用是将UniTaskVoid类型的异步逻辑包装成同步无返回值委托,完美适配Unity的UnityAction要求,同时遵循UniTask避免async void的最佳实践。
内容的提问来源于stack exchange,提问作者Aleksei Burov
相关产品推荐
相关产品推荐

