前面些时候要研究串口通讯,于是就接触到了多线程,期间呢,笔者想当然的把在线程里面使用了Unity 的 Text。然后就报错,然后呢就遇见了 Loom,嗯,设计的很巧妙,蛮好吃的!
所以,我就把自家曾经写的 Timer,重构了下,于是就有了本文,下面进入正题:
异常&解决方案
非主线程不能调用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 的能力,在主线程初始化不会这样麻烦。
测试代码
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 内运行,完美解决报错!
扩展阅读
- [Unity多线程(Thread)和主线程(MainThread)交互使用类——Loom工具分享] (https://dsqiu.iteye.com/blog/2028503)
- 重构后的 Timer -GitHub
-
Unity技术博客 - 客户端断点续传 - 简书 (这个演示了多线程中更新UI较为麻烦的一面)
他的麻烦在于:
逻辑跳跃跨度大,progress 要从线程中公开出来并在MonoBehaviour类中处理,由于下载完成回调只能使用基本类型,导致一个简单的 Complete 后加载场景的功能 逻辑四处分散。
使用 Timer 或者 Loom 将 OnComplete 回调埋进去,然后这个 回调就能 调用 Unity的API了,逻辑也就很容易做到不分散了(测试了下,死循环里面用Timer 会死,Loom不会,我的尴尬癌...)。
网友评论