Unity中完成Task后如何在主线程执行代码?
Unity异步Task完成后可靠切换主线程的解决方案
你的代码失败的核心原因是ContinueWith默认在后台线程执行,而Unity的StartCoroutine必须由主线程的MonoBehaviour实例调用,后台线程调用该方法不会触发协程执行。以下是两种可靠的解决方案:
方案一:利用Unity同步上下文直接切换
Unity 2019及以上版本支持SynchronizationContext,可在异步任务完成后直接切换到主线程上下文执行代码:
using UnityEngine; using System.Threading.Tasks; using System.Threading; public class AsyncMainThreadExample : MonoBehaviour { private void Start() { // 保存主线程的同步上下文 var mainContext = SynchronizationContext.Current; Task.Run(() => { // 执行耗时异步操作:网络请求、数据处理等 Debug.Log($"异步操作线程ID:{Thread.CurrentThread.ManagedThreadId}"); }) .ContinueWith(t => { // 处理异步任务的异常 if (t.Exception != null) { Debug.LogError($"异步任务出错:{t.Exception.Flatten()}"); return; } // 通过同步上下文切换到主线程 mainContext.Post(_ => { Debug.Log($"主线程ID:{Thread.CurrentThread.ManagedThreadId}"); // 这里执行UI更新、游戏对象修改等主线程操作 UpdateUI(); }, null); }); } private void UpdateUI() { // 示例:修改UI文本或游戏对象状态 // scoreText.text = "任务完成"; } }
方案二:可靠的单例主线程调度器
如果需要在多个场景或脚本中复用主线程切换逻辑,可实现一个线程安全的单例调度器,通过Update循环执行队列中的主线程动作:
调度器实现代码
using UnityEngine; using System.Collections.Generic; using System.Threading; public class MainThreadDispatcher : MonoBehaviour { private static MainThreadDispatcher _instance; private static readonly Queue<System.Action> _actionQueue = new Queue<System.Action>(); private static readonly object _lockObj = new object(); public static MainThreadDispatcher Instance { get { if (_instance == null) { var dispatcherObj = new GameObject("MainThreadDispatcher"); _instance = dispatcherObj.AddComponent<MainThreadDispatcher>(); DontDestroyOnLoad(dispatcherObj); } return _instance; } } private void Update() { lock (_lockObj) { while (_actionQueue.Count > 0) { var action = _actionQueue.Dequeue(); try { action.Invoke(); } catch (System.Exception ex) { Debug.LogError($"主线程执行动作出错:{ex}"); } } } } /// <summary> /// 将动作入队到主线程执行 /// </summary> public void Enqueue(System.Action action) { if (action == null) return; lock (_lockObj) _actionQueue.Enqueue(action); } /// <summary> /// 静态快捷方法,直接提交动作到主线程 /// </summary> public static void RunOnMainThread(System.Action action) { Instance.Enqueue(action); } }
调度器使用示例
using UnityEngine; using System.Threading.Tasks; public class AsyncTaskUsage : MonoBehaviour { private void Start() { Task.Run(async () => { try { // 模拟大型异步任务:比如发起网络请求并解析数据 await Task.Delay(2000); Debug.Log($"异步任务完成,线程ID:{System.Threading.Thread.CurrentThread.ManagedThreadId}"); // 提交主线程操作 MainThreadDispatcher.RunOnMainThread(() => { Debug.Log($"主线程执行更新,线程ID:{System.Threading.Thread.CurrentThread.ManagedThreadId}"); UpdateGameState(); }); } catch (System.Exception ex) { Debug.LogError($"异步任务异常:{ex}"); // 主线程显示错误提示 MainThreadDispatcher.RunOnMainThread(() => { // errorTipText.text = $"加载失败:{ex.Message}"; }); } }); } private void UpdateGameState() { // 示例:修改游戏对象位置或UI状态 // playerTransform.position = new Vector3(0, 1, 0); } }
关键注意事项
- 异常处理:异步任务中的异常必须在Task内部捕获,或在
ContinueWith中通过t.Exception获取,避免未捕获异常导致程序崩溃。 - 线程安全:调度器的队列操作必须加锁,防止多线程同时入队导致的队列损坏。
- 避免主线程耗时操作:主线程仅用于执行UI更新、游戏对象状态修改等必须主线程的操作,耗时计算仍留在异步线程中。
- 版本兼容:上述方案兼容Unity 2019+,你的2023.2.6f1 LTS版本完全适用。
内容的提问来源于stack exchange,提问作者Diogo Garcia
相关产品推荐
相关产品推荐

