Unity应用架构设计(10)——绕不开的协程和多线程(Part 2)

时间:2021-11-17 23:26:35

在上一回合谈到,客户端应用程序的所有操作都在主线程上进行,所以一些比较耗时的操作可以在异步线程上去进行,充分利用CPU的性能来达到程序的最佳性能。对于Unity而言,又提供了另外一种『异步』的概念,就是协程(Coroutine),通过反编译,它本质上还是在主线程上的优化手段,并不属于真正的多线程(Thread)。那么问题来了,怎样在Unity中使用多线程呢?

Thread 初步认识

虽然这不是什么难点,但我觉得还是有必要提一下多线程编程几个值得注意的事项:

  • 线程启动

在Unity中创建一个异步线程是非常简单的,直接使用类System.Threading.Thread就可以创建一个线程,线程启动之后毕竟要帮我们去完成某件事情。在编程领域,这件事就可以描述了一个方法,所以需要在构造函数中传入一个方法的名称。

Worker workerObject = new Worker();
Thread workerThread = new Thread(workerObject.DoWork)
workerThread.Start();
  • 线程终止

线程启动很简单,那么线程终止呢,是不是调用Abort方法。不是,虽然Thread对象提供了Abort方法,但并不推荐使用它,因为它并不会马上停止,如果涉及非托管代码的调用,还需要等待非托管代码的处理结果。

一般停止线程的方法是为线程设定一个条件变量,在线程的执行方法里设定一个循环,并以这个变量为判断条件,如果为false则跳出循环,线程结束。

public class Worker
{
public void DoWork()
{
while (!_shouldStop)
{
Console.WriteLine("worker thread: working...");
}
Console.WriteLine("worker thread: terminating gracefully.");
}
public void RequestStop()
{
_shouldStop = true;
}
private volatile bool _shouldStop;
}

所以,你可以在应用程序退出(OnApplicationQuit)时,将_shouldStop设置为true来到达线程的安全退出。

  • 共享数据处理

多线程最麻烦的一点就是共享数据的处理了,想象一下A,B两个线程同一时刻处理一个变量,它最终的值到底是什么。所以一般需要使用lock,但C#提供了另一个关键字volatile,告诉CPU不读缓存直接把最新的值返回。所以_shouldStopvolatile修饰。

Dispatcher的引入

是不是觉得多线程好简单,好像也没想象的那么复杂,当你愉快的在多线程中访问UI控件时,Duang~~~,一个错误告诉你,不能在异步线程访问UI控件。这是肯定的,跨线程访问UI控件是不安全的,理应被禁止。那怎么办呢?

如果你有其他客户端的开发经验,比如iOS或者WPF经验,肯定知道Dispatcher。Dispatcher翻译过来就是调度员的意思,简单理解就是每个线程都有唯一的调度员,那么主线程就有主线程的调度员,实际上我们的代码最终也是交给调度员去执行,所以要去访问UI线程上的控件,我们可以间接的向调度员发出命令。

所以在WPF中,跨线程访问UI控件一般的写法如下:

Thread thread=new Thread(()=>{
this.Dispatcher.Invoke(()=>{
//UI
this.textBox.text=...
this.progressBar.value=...
});
});

嗯~ o( ̄▽ ̄)o,不错,但尴尬的是Unity没有提供Dispatcher啊!

对,但我们可以自己实现,把握住几个关键点:

  • 自己的Dispatcher一定是一个MonoBehaviour,因为访问UI控件需要在主线程上
  • 什么时候去更新呢,考虑生产者-消费者模式,有任务来了,我就是更新到UI上
  • 在Unity中有这么个方法可以轮询是不是有任务要更新,那就是Update方法,每一帧会执行

所以自定义的UnityDispatcher提供一个BeginInvoke方法,并接送一个Action

public void BeginInvoke(Action action){
while (true) {
//以原子操作的形式,将 32 位有符号整数设置为指定的值并返回原始值。
if (0 == Interlocked.Exchange (ref _lock, 1)) {
//acquire lock
_wait.Enqueue(action);
_run = true;
//exist
Interlocked.Exchange (ref _lock,0);
break;
} } }

这是一个生产者,向队列里添加需要处理的Action。有了生产者之后,还需要消费者,Unity中的Update就是一个消费者,每一帧都会执行,所以如果队列里有任务,它就执行

 void Update(){

	if (_run) {
Queue<Action> execute = null;
//主线程不推荐使用lock关键字,防止block 线程,以至于deadlock
if (0 == Interlocked.Exchange (ref _lock, 1)) { execute = new Queue<Action>(_wait.Count); while(_wait.Count!=0){ Action action = _wait.Dequeue ();
execute.Enqueue (action); }
//finished
_run=false;
//release
Interlocked.Exchange (ref _lock,0);
}
//not block
if (execute != null) { while (execute.Count != 0) { Action action = execute.Dequeue ();
action ();
}
} }
}

值得注意的是,Queue不是线程安全的,所以需要锁,我使用了Interlocked.Exchange,好处是它以原子的操作来执行并且还不会阻塞线程,因为主线程本身任务繁重,所以我不推荐使用lock

Coroutine和MultiThreading混合使用

到目前为止,相信你对CoroutineThread有清楚的认识,但它们并不是互斥的,可以混合使用,比如Coroutine等待异步线程返回结果,假设异步线程里执行的是非常复杂的AI操作,这显然放在主线程会非常繁重。

由于篇幅有限,我不贴完整代码了,只分析其中最核心思路:

Thread中有一个WaitFor方法,它每一帧都会询问异步任务是否完成:

public bool Update(){
if(_isDown){
OnFinished ();
return true; }
return false;
}
public IEnumerator WaitFor(){
while(!Update()){
//暂停协同程序,下一帧再继续往下执行
yield return null;
}
}

那么在某一个UI线程中,等待异步线程的结果,注意利用StartCouroutine,此等待并非阻塞线程,相信你已经它内部的机制了。

void Start(){

    Debug.Log("Main Thread :"+Thread.CurrentThread.ManagedThreadId+" work!");
StartCoroutine (Move());
} IEnumerator Move()
{
pinkRect.transform.DOLocalMoveX(250, 1.0f);
yield return new WaitForSeconds(1);
pinkRect.transform.DOLocalMoveY(-150, 2);
yield return new WaitForSeconds(2);
//AI操作,陷入深思,在异步线程执行,GreenRect不会卡顿
job.Start();
yield return StartCoroutine (job.WaitFor());
pinkRect.transform.DOLocalMoveY(150, 2); }

小结

这两篇文章为大家介绍了怎样在Unity中使用协程和多线程,多线程其实不难,但同步数据是最麻烦的。Coroutine实际上就是IEnumeratoryield这两个语法糖让我们很难理解其中的奥秘,推荐使用反编译工具去查看,相信你会豁然开朗。

源代码托管在Github上,点击此了解

Unity应用架构设计(10)——绕不开的协程和多线程(Part 2)的更多相关文章

  1. Unity应用架构设计&lpar;10&rpar;————绕不开的协程和多线程(Part 1)

    在进入本章主题之前,我们必须要了解客户端应用程序都是单线程模型,即只有一个主线程(Main Thread),或者叫做UI线程,即所有的UI控件的创建和操作都是在主线程上完成的.而服务器端应用程序,也就 ...

  2. Unity应用架构设计&lpar;10&rpar;——绕不开的协程和多线程(Part 1)

    在进入本章主题之前,我们必须要了解客户端应用程序都是单线程模型,即只有一个主线程(Main Thread),或者叫做UI线程,即所有的UI控件的创建和操作都是在主线程上完成的.而服务器端应用程序,也就 ...

  3. 关于Unity中协程、多线程、线程锁、www网络类的使用

    协程 我们要下载一张图片,加载一个资源,这个时候一定不是一下子就加载好的,或者说我们不一定要等它下载好了才进行其他操作,如果那样的话我就就卡在了下载图片那个地方,傻住了.我们希望我们只要一启动加载的命 ...

  4. 学习PYTHON之路, DAY 10 进程、线程、协程篇

    线程 线程是应用程序中工作的最小单元.它被包含在进程之中,是进程中的实际运作单位.一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务. 直接调用 impo ...

  5. UNITY所谓的异步加载几乎全部是协程,不是线程&semi;MAP3加载时解压非常慢

    实践证明,以下东西都是协程,并非线程(thread): 1,WWW 2,AssetBundle.LoadFromFileAsync 3,LoadSceneAsync 其它未经测试 此问题的提出是由于一 ...

  6. Unity应用架构设计&lpar;11&rpar;——一个网络层的构建

    对于客户端应用程序,免不了和远程服务打交道.设计一个良好的『服务层』能帮我们规范和分离业务代码,提高生产效率.服务层最核心的模块一定是怎样发送请求,虽然Mono提供了很多C#网络请求类,诸如WebCl ...

  7. Unity应用架构设计&lpar;13&rpar;——日志组件的实施

    对于应用程序而言,日志是非常重要的功能,通过日志,我们可以跟踪应用程序的数据状态,记录Crash的日志可以帮助我们分析应用程序崩溃的原因,我们甚至可以通过日志来进行性能的监控.总之,日志的好处很多,特 ...

  8. Unity应用架构设计&lpar;6&rpar;——设计动态数据集合ObservableList

    什么是 『动态数据集合』 ?简而言之,就是当集合添加.删除项目或者重置时,能提供一种通知机制,告诉UI动态更新界面.有经验的程序员脑海里迸出的第一个词就是 ObservableCollection.没 ...

  9. Unity应用架构设计&lpar;1&rpar;—— MVVM 模式的设计和实施&lpar;Part 2&rpar;

    MVVM回顾 经过上一篇文章的介绍,相信你对MVVM的设计思想有所了解.MVVM的核心思想就是解耦,View与ViewModel应该感受不到彼此的存在. View只关心怎样渲染,而ViewModel只 ...

随机推荐

  1. JavaScript倒计时

    倒计时: 1.设置一个有效的结束日期 2.计算剩余时间 3.将时间转换成可用的格式 4.输出时钟数据作为一个可重用的对象 5.在页面上显示时钟,并在它到达0时停止 <div id="c ...

  2. 要用于尝试,广东移动间接实现&OpenCurlyDoubleQuote;流量不清零”&lbrack;bubuko&period;com&rsqb;

    拥有1亿用户的广东移动在推出流量共享后,推出4G套餐外流量的“自动升档”服务,每月根据客户消费情况动态自动匹配当月最恰当的一档流量资费.未来,还将推出“流量转赠”服务,用不完的流量可转赠给其他用户. ...

  3. Linux 进程详解

    Linux内核的七大区间 .进程管理(进程创建,进程的三种状态,进程间的调度,调度算法...) .内存管理(段式管理(Linux所有段都从0开始),页式管理--地址偏移量) .系统调用(C语言库函数的 ...

  4. LINQ查询操作符之First、FirstOrDefault、Last、LastOrDefault、ElementAt、ElementAtOrDefault、Contains、Any、All、Coun

    ·First - 返回集合中的第一个元素:不延迟 ·FirstOrDefault - 返回集合中的第一个元素(如果没有则返回默认值):不延迟 ·Last - 返回集合中的最后一个元素:不延迟 ·Las ...

  5. codeforces 687B - Remainders Game 数学相关(互质中国剩余定理)

    题意:给你x%ci=bi(x未知),是否能确定x%k的值(k已知) ——数学相关知识: 首先:我们知道一些事情,对于k,假设有ci%k==0,那么一定能确定x%k的值,比如k=5和ci=20,知道x% ...

  6. ARM-Linux驱动-触摸屏驱动分析

    出处:http://blog.csdn.net/geekcome/article/details/6580981 硬件平台:FL2440 内核版本:2.6.28 主机平台:Ubuntu 11.04 内 ...

  7. 解决的方法:warning&colon; Clock skew detected&period; Your build may be incomplete&period;

    因为时钟同步问题.出现 warning:  Clock skew detected.  Your build may be incomplete.这种警告, 解决的方法: find . -type f ...

  8. 打开即时通讯服务器openfire的大门

    1.什么是即时通讯 你现在最常用的软件是什么,如果我没有猜错,应该是QQ和微信,是的,他们就是即时通讯软件. 一个可以让你无时无刻,只要有网络就能够沟通的工具,就是即时通讯工具.那么本教程,我们主要以 ...

  9. 不需要更多JS框架了

    现在,JavaScript框架已成为Web项目开发不可或缺的一部分.那是因为很长一段时间以来,各种浏览器之间有很大的差别,人们不得不编写框架对此进行屏蔽.问题在于,各种浏览器甚至在基本问题上都难以取得 ...

  10. KNN PCA LDA

    http://blog.csdn.net/scyscyao/article/details/5987581 这学期选了门模式识别的课.发现最常见的一种情况就是,书上写的老师ppt上写的都看不懂,然后绕 ...