我目前正在开发一个Unity项目,我需要使用任务异步执行某些操作,然后在主线程上应用结果(例如更新UI或修改游戏对象)。虽然我知道 Unity 的主线程对于此类操作至关重要,以避免运行时错误,但我在确保代码执行完成任务后返回主线程方面遇到了困难。
我已经尝试了几种方法来解决这个问题,包括:
协程:尝试从任务的延续中启动协程,但协程似乎没有按预期方式执行,或者没有按照我的预期转换回主线程。
自定义调度程序类:创建的类类似于通常称为 UnityMainThreadDispatcher、Dispatcher 和 MainThreadExecutor 的类,这些类应该将更新循环期间在主线程上执行的操作排队。然而,这些解决方案对于我的需求来说并不可靠,要么是因为根本没有执行,要么是其他同步问题。
这是我想要实现的目标的简化版本:
Task.Run(() =>
{
// Perform some asynchronous operation
Debug.Log("Perform some asynchronous operation");
})
.ContinueWith(t => StartCoroutine(ExecuteOnMainThread()));
private IEnumerator ExecuteOnMainThread()
{
// Code that needs to run on the main thread, e.g., UI updates
Debug.Log("Code that needs to run on the main thread, e.g., UI updates");
yield return null;
}
如果我执行此代码,只会弹出“执行某些异步操作”,它永远不会显示“需要在主线程上运行的代码,例如 UI 更新”。
我在网上看到了各种建议和代码片段,但没有一个在我的场景中完美运行。我正在联系,看看是否有人成功地从 Unity 中的任务中在主线程上执行代码,并且愿意分享他们的方法或见解。
具体来说,我正在寻找一个解决方案:
在不同的 Unity 版本之间可靠(我目前使用的是 Unity 2023.2.6f1 LTS)。 适用于涉及大量异步操作的任务,包括 Web 请求和数据处理。 能够处理任务中可能出现的异常,保证主线程仍然能够安全执行。 如果您遇到过类似的挑战并找到了可行的解决方案,或者您对改进我尝试过的方法有任何建议,我将非常感谢您的建议。
提前感谢您的帮助!
这个问题可以通过多种方式解决,但我将分享我最喜欢的在网络库中广泛使用的问题。 创建一个操作队列(如果您想让它更干净,您可以创建一个调用这些操作的对象队列),在需要更新 UI 时添加一个操作,并在 Unity 更新/后期更新中检查新操作。当您在队列中找到任何元素时,执行它们并将它们从队列中删除。 这可确保所有执行调用始终在 Unity 线程上,因此您可以将 Unity 组件修改为 UI 元素,同时保留在 Unity 线程外部修改队列的能力。
感谢@adrian-sgro 的回复,这帮助我弄清楚如何解决我的问题。我想分享我在任务中在 Unity 主线程上执行代码的任务的更新。在尝试了各种方法(包括像 UnityMainThreadDispatcher 这样的自定义调度程序类)之后,我找到了适合我的特定情况的解决方案。
最初,我的尝试涉及创建一个单独的类来管理需要在主线程上执行的操作(Action)队列。尽管确保此类已附加到活动的 GameObject 并且启用了脚本,但 Unity 并未按预期调用此调度程序类中的 Update 方法。
这是我解决问题的方法:
我将操作队列直接移至主类中,其中已经有其他特定于 Unity 的逻辑(例如处理用户输入或更新游戏状态)。这是我所做的简化结构:
声明操作队列: 在我的主要 MonoBehaviour 类中,我声明了一个私有静态操作队列。
private static Queue<Action> actions = new Queue<Action>();
将任务中的操作入队: 每当我需要在主线程上执行某些任务作为任务完成的结果时,我都会像这样将操作排入队列:
actions.Enqueue(() =>
{
Debug.Log("Test Enqueue from Task");
});
在Update中处理队列: 我在同一个 MonoBehaviour 类的 Update 方法中添加了一个循环来处理和执行操作。
while (actions.Count > 0)
{
Debug.Log("Update called with " + actions.Count + " actions in queue");
Action action = null;
lock (actions)
{
if (actions.Count > 0)
{
action = actions.Dequeue();
}
}
action?.Invoke();
}
结论:
似乎由于某种原因,单独的类已正确初始化,但 Unity 并未调用其 Update 方法。通过将操作队列直接集成到我的主 MonoBehaviour 类中,我确保从任务排队的操作按预期在主线程上执行。
这种方法可能并不适合所有场景,但它解决了我在异步操作之后在主线程上执行代码的具体问题。
我希望这个解决方案可以帮助其他面临类似挑战的人。如果有人知道为什么在单独的类场景中可能没有调用 Update 方法,我洗耳恭听!