前面些时候要研究串口通讯,于是就接触到了多线程,期间呢,笔者想当然的把在线程里面使用了Unity 的 Text。然后就报错,然后呢就遇见了 Loom,嗯,设计的很巧妙,蛮好吃的!
所以,我就把自家曾经写的 Timer,重构了下,于是就有了本文,下面进入正题:
异常&解决方案
Loom的设计
就是把操作Unity组件的逻辑块使用 Action 包裹埋入到非主线程的上下文,但这个线程执行到这个位置就把这些个 Action 抛入MonoBehaviour的 Update中执行他 ,实现的效果如下:
- 更优的代码的可读性和逻辑连续性
- 更小范围的数据可见性(闭包优势)
- 实时与 Unity 组件交互(闭包优势)
Timer的设计
只因为多看了一眼,才发现 Timer 的 TimerDriver 理念 原来跟这个 Loom 是那么的相近:
都是利用委托Action 把逻辑块插入其他逻辑块的上下文,然后利用闭包的优势共享这个被插逻辑块上下文的局部变量。
然后其实呢,执行这个 Action 的是另一个继承了MonoBehaviour 的类,在Timer中 我谓之:TimerDirver。
小结
综上,这个Loom 带来的 Unity多线程 炫酷体验,只需要简单的重构,俺家的 Tiemr 也必定兼并你的特色功能,下面就是重构大体思路
重构 Timer
- 剔除Timer 中 UnityEgine 相关的API : Time.realtimeSinceStartup 、Time.time。
- 将上述剔除的 CurrentTime 实际驱动 放到 TimerDriver Update中。
- 为线程安全,对定时器链表 List<Timer> timers 各处上锁。
- 如果在非主线程中TimerDriver初始化会报错,新增
Timer.IntializeDriver()
,提供手动初始化TimerDriver 的能力,在主线程初始化不会这样麻烦。
应用场景1-简单的应用
using UnityEngine;
using System.Threading.Tasks;
using QFramework.TimeExtend;
using Timer = QFramework.TimeExtend.Timer;
using UnityEngine.UI;
public class TestForTimer : MonoBehaviour
{
public Text text;
private void Awake()
{
Timer.IntializeDriver(); //首次初始化不能放在非主线程内。
}
void Update()
{
if (Input.GetKeyDown(KeyCode.R))
{
this.FunctionA();
}
}
internal void FunctionA()
{
Task task = new Task(() =>
{
string _name = "CreateWithTimer ←";
Timer.AddTimer(0).OnCompleted(() =>
{
text.text = _name; //先演示异常
new GameObject(_name);
});
});
task.Start();
}
}
动画演示
Tips:
先尝试在 Task 内直接更新 Text组件数据 ,失败!
再尝试 Timer 内运行,完美解决报错!
应用场景2 - Http下载
using UnityEngine;
using Timer = QFramework.TimeExtend.Timer;
using System;
using System.IO;
using UnityEngine.UI;
public class TestDownload : MonoBehaviour
{
public string url = "http://localhost:8083/bigFile";
public string savePath = "";
public Text finish;
public Text update;
HttpDownLoader DownLoader;
void Awake()
{
savePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "TestDownLoad");
Timer.IntializeDriver();
Loom.Initialize();
DownLoader = new HttpDownLoader();
DownLoader.OnDownLoadCompleted.AddListener(() =>
{
this.finish.text = "下载完成!";
});
DownLoader.OnDownLoadUpdate.AddListener(v =>
{
this.update.text = string.Format("下载进度:{0} %", (v * 100).ToString("f2"));
});
}
private void Start()
{
this.DownLoader.DownLoad(url, savePath);
}
private void OnDisable()
{
this.DownLoader.Close();
}
}
动画演示
Tips:
使用 Timer 或者 Loom 将 OnComplete 和OnUpdate 回调埋进去,实现事件驱动的进度刷新和下载完成提示,无需额外的判断。
扩展阅读
Unity非主进程内访问Unity组件报错、怎么在其他进程直接操作Unity组件、Unity多线程
可以做为 Loom 插件的课外知识,但不保证这个Timer 能够合理的处理高并发,毕竟笔者是个菜鸡儿,尤其是多线程编程。