文章目录
- 前言
- 一、unity真的不支持多线程吗?
- 1.unity中使用多线程
- 2.unity中多线程的停止
- 3.unity中使用多线程的问题
- 二、协同程序
- 1.协程的使用
- 2.协程的原理
- 总结
前言
Unity中的协程,即协同程序,是一个很好用的工具,我们在很多时候都会用到,但协程究竟是怎样的机制?和线程是什么样的区别?下面让我们来一探究竟。
一、unity真的不支持多线程吗?
首先,unity中是支持多线程的。初学unity时看到有些地方说,unity中不支持多线程,就没怎么尝试过。之后进一步学习时才更多的了解了一些,下面通过几个例子来看一下unity中多线程的使用。
1.unity中使用多线程
首先在unity场景中创建一个空物体,创建一个新的C#脚本,在类中定义一个方法,循环打印1,再定义一个线程对象,在Start()生命周期函数中开启线程。
代码如下:
Thread t;
// Start is called before the first frame update
void Start()
{
t = new Thread(Test);
t.Start();
}
void Test()
{
while (true)
{
print(1);
}
}
将脚本挂载到场景上的空物体,运行,发现可以正常打印出1,也没有报错。
2.unity中多线程的停止
如果做到了这一步,接下来会发现,即使停止运行,程序仍在继续打印1。尝试将线程对象改为后台线程,重新运行:
代码如下:
t.IsBackground = true;
发现仍然不停打印。原因,其实UnityEditor本身就是这个软件程序中的一根线程,当我们在脚本中新开一个线程时,它是和UnityEditor同步执行的,所以我们停止了UnityEditor的运行并不会影响这个线程的执行。解决方案:在脚本的生命周期函数OnDestory()中停止线程,这样停止运行时会调用场景上对象的OnDestory()方法,就会停止其它线程。
代码如下:
void OnDestroy()
{
t.Abort();
}
3.unity中使用多线程的问题
下面我们再通过一个例子看一下,为什么有unity中不支持多线程的这种说法。还是上述脚本,我们改一下Test方法:
代码如下:
Thread t;
// Start is called before the first frame update
void Start()
{
t = new Thread(Test);
t.Start();
}
void Test()
{
while (true)
{
print(transform.position);
}
}
注意这里将打印结果改为了transform.position,运行,发现报了这样一个错误:
提示get_transform是Component类中的一个属性中的get方法,涉及到unity中的一些反射机制,这里不做过多描述,简单来说提示的意思就是我们要打印的transform.position只能在主线程中执行。实际上我们在脚本中新开的线程无法访问绝大部分unity中的对象,都会报这个错误,所以会有unity不支持多线程的这种说法,但实际上这种说法并不完全正确。
虽然新开的线程无法访问unity中的很多对象,但是我们可以将一些复杂的算法计算、网络连接等逻辑抛给一个新的线程去处理,将处理的数据放在公共内存模块中,在unity主线程就可以访问使用了,关于这种案例我们之后再讨论,前面说了很多关于多线程的问题,下面来讲一下unity对于以上问题机制给出的解决方案——协程。
二、协同程序
协同程序简称协程,多在资源、场景异步加载等逻辑时使用,它的使用效果和线程有些类似,都可以在不卡住主程序的情况下开启另一段逻辑的执行,但是它和线程有本质上的区别,协程的执行是在主线程下,而不是在另一个线程中,所以它可以访问unity中的所有对象,而不会出现上述使用线程中遇到的问题。
1.协程的使用
协程的基本使用:声明一个返回值为IEnumerator的方法,用然后用MonoBehaviour中的StartCoroutine()方法开启协程。
代码如下:
void Start()
{
//开启协程返回一个协程对象
Coroutine co = StartCoroutine(Test());
//关闭协程
StopCoroutine(co);
}
IEnumerator Test()
{
while (true)
{
print(1);
//要有一个yield修饰的返回值
yield return new WaitForSeconds(1);
}
}
协程方法是每秒钟打印一次1,这里关于协程的返回值暂时不做过多介绍,之后可能在别的文章中具体说明,读者也可看一下unity官方的描述,下面着重讲一下协程原理。
2.协程的原理
协程本质上包括两部分:
1、协程方法
2、协程调度器
协程方法即返回值为IEnumerator接口类型或其子类的方法,本质上是一个迭代器方法,不了解C#迭代器的读者可以先补充一下这方面的知识。我们进入IEnumerator接口看一下:
public interface IEnumerator
{
//表示方法当前执行到的返回值
object Current { get; }
//继续执行方法,返回表示是否执行完毕
bool MoveNext();
//重置方法的执行位置
void Reset();
}
我们不使用MonoBehaviour的StartCoroutine方法,来执行一个协程:
代码如下:
void Start()
{
IEnumerator ie = Test();
print(ie.MoveNext());
print(ie.Current);
print(ie.MoveNext());
print(ie.Current);
print(ie.MoveNext());
print(ie.Current);
print(ie.MoveNext());
print(ie.Current);
}
IEnumerator Test()
{
print(1);
yield return 1;
print(2);
yield return "哈哈";
print(3);
yield return 3.14;
print(4);
yield return gameObject;
}
unity中的运行结果:
由此可见,所谓协同程序,就是一步步的执行迭代器对象中的MoveNext()方法,调用MoveNext()方法会执行下一个yield return之前的逻辑,并且根据MoveNext()的返回值判断是否全部执行完毕,而通过yield return返回Current成员对象,来判断下一次执行MoveNext()的时机。而这些工作,就是由协同程序的第二部分——协程调度器来实现的。这里的协程调度器是unity引擎实现的,理论上我们是可以自己去实现一个协程调度器的,大家感兴趣的话可以自己实现一个,能进一步加深对协程的理解。
总结
总之,协同程序的存在很大程度上弥补了unity中使用多线程出现的问题,但在原理上与线程有本质上的区别,我们在使用时也要依据情景,选择更适合的方式。另外,文章中并没有过多的阐述协程的使用,如果读者没有这部分的基础需要自己学习一下,之后可能会单独发一篇文章来详细描述协程的使用。