最近,我不得不设计一个计时器系统来周期性地生成球形对象。这是密码。如果你分配球的obj,产卵项,那么它可能会起作用。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Threading;
using System;
using System.Collections.Generic;
public class BallCup: MonoBehaviour
{
bool wait = false;
public float respawnTerm = 3;
public GameObject Ball;
public Vector3 spawnpoint;
// Start is called before the first frame update
void Start()
{
spawnpoint = transform.position - new Vector3(0, 0, 10);
}
// Update is called once per frame
void Update()
{
createBallRegularly();
}
void createBallRegularly()
{
if (!wait)
{
StartCoroutine("startTimer");
}
else
{
return;
}
}
IEnumerator startTimer()
{
wait = true;
//cs entered
Instantiate(Ball, spawnpoint, Quaternion.identity);
yield return new WaitForSeconds(respawnTerm);
//cs ended
wait = false;
}
}我读过一些关于定时器和同步问题的文章。解决同步问题有两种方法。一种方法是投票,我不记得它叫什么,但它是睡觉和醒来。
在UnityManual中,StartCoroutine是创建一个新的工作线程(任务),以独立运行到主任务。
问题是上面的代码,我每次调用该方法来检查临界部分是否空闲。
我读过这个。但是在我的计时器的代码和实现之间有一些差距。如何为C#编写线程安全的Unity3D代码?
发布于 2021-12-31 13:50:53
首先,存在一个相当大的误解:柯鲁丁基本上就像一个小的临时Update方法,底层IEnumerator的每个MoveNext步骤都是通过underlying线程中的消息执行的。=> 这绝对与多线程无关!
StartCoroutine是创建一个新的工作线程(任务),以便独立运行到主任务。
我不知道你读过哪本手册,但不,这完全是胡说八道!
来自实际手册
在联合中,协同线是一种方法,它可以暂停执行并将控制返回给Unity,但在下面的帧上继续执行。
尤其是
但是,重要的是要记住,协同线不是线程。在协同线中运行的同步操作仍然在主线程上执行。
当您调用StartCoroutine时会发生什么(简单地说--我也无法访问底层源代码)
IEnumerator在此组件上注册为运行的Coroutine。yield语句yielded 到底是什么?yield return null;基本上意味着只产生一次,然后在下一个Update调用之后继续下一个帧中的其余代码。yield return new WaitForSeconds (3); afaik基本上检查帧一次,时间是否已经过去,然后在下一次Update调用完成后继续进行。YieldInstruction,比如WaitForEndOfFrame,它基本上在一个特殊消息事件调用(EndOfFrame)的队列中注册这个IEnumerator,然后继续执行将它移回正常的Coroutine队列(参见事件执行顺序)。所有这些事件都在统一主线程中执行。
因此,像您这样使用Coroutine是相当多余的。
要么简单地使用
public class BallCup: MonoBehaviour
{
bool wait = false;
public float respawnTerm = 3;
public GameObject Ball;
public Vector3 spawnpoint;
// Yes indeed Start can be IEnumerator and Unity will run it as Coroutine automatically
IEnumerator Start()
{
spawnpoint = transform.position - new Vector3(0, 0, 10);
while(true)
{
Instantiate(Ball, spawnpoint, Quaternion.identity);
yield return new WaitForSeconds(respawnTerm);
}
}
}或者如果您喜欢在Update中不使用Coroutine就这样做
public class BallCup: MonoBehaviour
{
bool wait = false;
public float respawnTerm = 3;
public GameObject Ball;
public Vector3 spawnpoint;
private float timer:
void Start()
{
spawnpoint = transform.position - new Vector3(0, 0, 10);
}
private void Update ()
{
timer -= Time.deltaTime;
if(timer <= 0f)
{
Instantiate(Ball, spawnpoint, Quaternion.identity);
timer = respawnTerm:
}
}
}或者更简单地使用InvokeRepeating和do
public class BallCup: MonoBehaviour
{
bool wait = false;
public float respawnTerm = 3;
public GameObject Ball;
public Vector3 spawnpoint;
void Start()
{
spawnpoint = transform.position - new Vector3(0, 0, 10);
InvokeRepeating (nameof (SpawnBall), 0f, respawnTerm);
}
private void SpawnBall ()
{
Instantiate(Ball, spawnpoint, Quaternion.identity);
}
}所以对于你的问题
是那种投票吗?
不,不是真的。您根本不必担心同步,因为Unity已经为您做了这件事。当然,在内部,它可能被认为是“轮询的类型”,因为如果时间已经过去了,您可以检查一次帧(但是,这也可能是完全不同的处理方式--我们没有源代码)。“这类轮询”可能指的是
while(!someCondition)
{
Thread.Sleep(50);
}甚至更糟
while(! someCondition) { }应该避免这样做。
当然,您是如何使用它的,正如前面提到的,它是一种冗余的轮询实现I Update,但是,除了非常冗余之外,您不必担心性能问题。
如果我不想每次调用createBallRegularly()的话。有什么方法可以像java线程那样实现吗?
好的,如上面所示,有多种更好的方法来实现您的行为,这些方法在Update中不使用标志轮询。
我听说这个任务与线程类似,那么使用'startcoroutine‘以获得更好的性能会更好吗?
如前所述,Coroutines既不是Task,也不是Thread。
一般的问题是何时使用Task和何时使用Thread是一个棘手的问题。作为一种拇指规则,我认为Task通常只需要很短的时间,或者是“一次的事情”。另一方面,如果某物的运行时间较长,则应首选Thread。一些定期收到工作包的工作线程。两者各有优缺点。
发布于 2021-12-31 11:48:50
我真的不知道你想要满足什么条件,但有几种方法可以让这件事变得更好。
如果您想在球被销毁时启动协同线,则需要使void createBallRegularly()公开,并在Start()中调用此方法,然后每次实例化球被销毁时。
如果不只是删除bool,那么在start方法中调用coroutine,让它继续运行,而无需在更新中调用它。
发布于 2021-12-31 03:44:55
https://stackoverflow.com/questions/70538568
复制相似问题