众所周知,Unity有自己的主线程,所有的生命周期函数和特性都在那里执行。早期,我们被告知尽量避免使用线程,而尽可能使用协程(Coroutine),因为Unity不是线程安全的。然而,协程并不总能发挥作用,尤其是在主线程的生命周期中运行的特定时段。考虑那些阻塞线程的执行场景。如果我们在主线程中进行套接字连接,应用程序将会卡住。在处理一个文件传输的bug时,我发现了在Unity中使用线程(Thread)时一些重要的注意事项。因此,我们应该在Unity中正确且安全地使用线程。
修复线程停止的Bug
我修复的bug与停止线程有关。我发现调用Thread.Abort()
来停止先前的线程,然后使用Thread.Start()
来启动具有相同任务函数的新线程,会在任务函数中的变量中导致意想不到的结果。
Thread taskThread;
void Task() {
// 执行某些操作,并拥有局部变量。
while(true) {
// ...
}
}
void StartTask() {
if (taskThread != null && taskThread.IsAlive)
taskThread.Abort();
taskThread = new Thread(Task);
taskThread.Start();
// 在Task()中的局部变量出现了一些意想不到的结果
}
// 在Unity的MonoBehaviour中的某处调用
StartTask();
也许我应该等待一段时间后再调用Thread.Abort()
来检查Thread.IsAlive
。但有更好的方法来停止线程。设置一个布尔标志!调用Thread.Abort()
可能会抛出ThreadAbortException
异常。因此,我的解决方案如下:
bool runThread = false;
Thread taskThread;
Coroutine startTaskCoroutine;
void Task() {
// 执行某些操作,并拥有局部变量。
while(runThread) {
// ...
}
}
IEnumerator StartTask() {
runThread = false;
while(taskThread != null && taskThread.IsAlive)
yield return null;
runThread = true;
taskThread = new Thread(Task);
taskThread.Start();
}
// 在Unity的MonoBehaviour中的某处调用
if (startTaskCoroutine != null)
StopCoroutine(startTaskCoroutine);
startTaskCoroutine = StartCoroutine(StartTask());
结论
- 我们应该在什么情况下使用线程而不是协程?
- 阻塞主线程的执行。
- 可以在后台运行的复杂计算。
- 与没有Unity API的原生代码插件通信。
- 需要注意什么?
- 避免在自定义线程中使用Unity API。
- 使用布尔标志来停止线程,而不是
Thread.Abort()
。 - 注意
lock()
的使用。