完整教程:u3d跨线程UI通知的实现原理和使用方法
完整的实现步骤
1. 首先创建主线程调度器(必需)
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class UnityMainThreadDispatcher : MonoBehaviour
{
private static readonly Queue _executionQueue = new Queue();
private static UnityMainThreadDispatcher _instance = null;
public static UnityMainThreadDispatcher Instance()
{
if (!Exists())
{
// 如果没有实例,创建一个
GameObject go = new GameObject("MainThreadDispatcher");
_instance = go.AddComponent();
DontDestroyOnLoad(go);
}
return _instance;
}
public static bool Exists()
{
return _instance != null;
}
public void Enqueue(Action action)
{
lock(_executionQueue)
{
_executionQueue.Enqueue(action);
}
}
void Update()
{
lock(_executionQueue)
{
while (_executionQueue.Count > 0)
{
_executionQueue.Dequeue().Invoke();
}
}
}
void Awake()
{
if (_instance == null)
{
_instance = this;
DontDestroyOnLoad(this.gameObject);
}
else if (_instance != this)
{
Destroy(gameObject);
}
}
void OnDestroy()
{
_instance = null;
}
}
2. 创建事件管理器
using System;
using UnityEngine;
public static class ThreadEventManager
{
// 定义事件
public static event Action OnThreadCompleted;
public static event Action OnProgressUpdated;
// 线程安全的事件触发方法
public static void TriggerThreadCompleted(string message)
{
// 将UI更新操作调度到主线程
UnityMainThreadDispatcher.Instance().Enqueue(() => {
OnThreadCompleted?.Invoke(message);
});
}
public static void TriggerProgressUpdated(int progress)
{
UnityMainThreadDispatcher.Instance().Enqueue(() => {
OnProgressUpdated?.Invoke(progress);
});
}
}
3. 创建执行后台线程的类
using System.Threading;
using UnityEngine;
public class BackgroundTaskRunner : MonoBehaviour
{
void Start()
{
// 启动后台任务
StartBackgroundTask();
}
void StartBackgroundTask()
{
Thread backgroundThread = new Thread(DoBackgroundWork);
backgroundThread.Start();
}
// 这个方法在后台线程中运行
void DoBackgroundWork()
{
Debug.Log("后台任务开始执行...");
// 模拟耗时任务,分步骤执行
for (int i = 0; i <= 100; i += 10)
{
Thread.Sleep(500); // 模拟工作耗时
// 通知UI更新进度(从后台线程调用)
ThreadEventManager.TriggerProgressUpdated(i);
Debug.Log($"后台线程: 进度 {i}%");
}
// 任务完成,通知UI
ThreadEventManager.TriggerThreadCompleted("后台任务已完成!");
Debug.Log("后台任务执行完毕");
}
}
4. UI界面控制器(你提供的代码)
using UnityEngine;
using UnityEngine.UI;
public class UIController : MonoBehaviour
{
[SerializeField] private Text statusText;
[SerializeField] private Slider progressSlider;
void Start()
{
// 订阅事件 - 当其他线程触发事件时,这些方法会被调用
ThreadEventManager.OnThreadCompleted += HandleThreadCompleted;
ThreadEventManager.OnProgressUpdated += HandleProgressUpdated;
Debug.Log("UI界面已订阅线程事件");
}
// 处理线程完成事件
void HandleThreadCompleted(string message)
{
statusText.text = message;
Debug.Log($"UI收到完成通知: {message}");
}
// 处理进度更新事件
void HandleProgressUpdated(int progress)
{
progressSlider.value = progress / 100f; // Slider的值通常是0-1
Debug.Log($"UI更新进度: {progress}%");
}
void OnDestroy()
{
// 重要:取消订阅,防止内存泄漏
ThreadEventManager.OnThreadCompleted -= HandleThreadCompleted;
ThreadEventManager.OnProgressUpdated -= HandleProgressUpdated;
}
}
具体使用场景示例
场景1:网络请求完成后通知UI
public class NetworkManager : MonoBehaviour
{
public void DownloadData()
{
Thread downloadThread = new Thread(() => {
try
{
// 模拟网络下载
for (int i = 0; i <= 100; i += 5)
{
Thread.Sleep(100);
ThreadEventManager.TriggerProgressUpdated(i);
}
// 下载完成
ThreadEventManager.TriggerThreadCompleted("数据下载完成!");
}
catch (System.Exception ex)
{
ThreadEventManager.TriggerThreadCompleted($"下载失败: {ex.Message}");
}
});
downloadThread.Start();
}
}
场景2:文件处理完成后通知UI
public class FileProcessor : MonoBehaviour
{
public void ProcessLargeFile(string filePath)
{
Thread processingThread = new Thread(() => {
// 模拟文件处理
string[] steps = {"读取文件", "解析数据", "验证内容", "保存结果"};
for (int i = 0; i < steps.Length; i++)
{
Thread.Sleep(1000); // 模拟处理时间
int progress = (i + 1) * 25; // 25%, 50%, 75%, 100%
ThreadEventManager.TriggerProgressUpdated(progress);
// 如果是最后一步
if (i == steps.Length - 1)
{
ThreadEventManager.TriggerThreadCompleted("文件处理完成!");
}
}
});
processingThread.Start();
}
}
实现原理解析
事件订阅机制:
// UIController在Start()中订阅事件 ThreadEventManager.OnThreadCompleted += HandleThreadCompleted;
跨线程安全调用:
// 后台线程通过事件管理器触发事件 ThreadEventManager.TriggerThreadCompleted("消息"); // 事件管理器确保UI更新在主线程执行 UnityMainThreadDispatcher.Instance().Enqueue(() => { OnThreadCompleted?.Invoke(message); });
主线程调度:
// UnityMainThreadDispatcher在每帧Update中执行队列中的操作 void Update() { while (_executionQueue.Count > 0) { _executionQueue.Dequeue().Invoke(); // 在主线程执行UI更新 } }
关键要点
- 线程安全:所有UI操作必须在主线程中执行
- 事件解耦:发送方(后台线程)和接收方(UI)完全解耦
- 内存管理:记得在OnDestroy中取消事件订阅
- 异常处理:在后台线程中要处理可能的异常
这样,你就可以从任何后台线程安全地通知和更新Unity的UI界面了!