Home > Net >  Unity StartCoroutine
Unity StartCoroutine

Time:12-31

I recently had to design a timer system to generate the ball object periodically. Here's the code. If you assign the ball obj, spawn term then it might work.

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;

    }

}

I have read some articles about timer and synchronization problems. There was two way to solve the synchronization problem. one way was polling, and I don't remember what it calls, but it was sleeping and waking up.

In UnityManual, StartCoroutine is create a new worker thread(task) to run independently to the main task.

Question is Above code, I call the method every time to check whether the critical section is free

  1. Is it the kinds of polling?

  2. If I don't want to call every time createBallRegularly(). Is there any way to implement like java thread?

  3. I heard the task is similar with thread, then is it might better to use 'startcoroutine' for better performance.

I've had read this. but there's some gap in between the code and implementation of my timer. How to write thread-safe C# code for Unity3D?

and I also checked this. https://web.mit.edu/6.005/www/fa15/classes/23-locks/

CodePudding user response:

Have you read this? It has examples。

Other things to note:

  1. Coroutine is Run in main Thread,improper use will block the process
  2. StartCoroutine Causes a class to be instantiated,Use it too many times is costly,Select "while" or "for" substitution

CodePudding user response:

I can't really tell what conditions you are wanting to meet but there are a few ways you can make this better.

If you want to start the coroutine when a ball is destroyed you need to make void createBallRegularly() public and call this method in Start() and then every time the the instantiated ball is destroyed.

if not just remove the bool, call the coroutine in the start method and let it keep running without calling it in the update.

CodePudding user response:

First of all there is quite a misunderstanding: A Coroutine is basically like a small temporary Update method and each MoveNext step of the underlying IEnumerator is executed by the Unity framework via messages in the Unity main thread. => This has absolutely nothing to do with multithreading!

StartCoroutine is create a new worker thread(task) to run independently to the main task.

I don't know which Manual you have read but no, this is absolute nonsense!

From the actual Manual

In Unity, a coroutine is a method that can pause execution and return control to Unity but then continue where it left off on the following frame.

and especially

However, it’s important to remember that coroutines aren’t threads. Synchronous operations that run within a coroutine still execute on the main thread.

What happens when you call StartCoroutine is rather (in simple words - I don't have access to the underlying source code either)

  • The IEnumerator is registered as a running Coroutine on this component
  • It also immediately executes everything until the first yield statement
  • Then it depends on what exactly is yielded
    • yield return null; basically means yield once, then continue with the rest of code in the next frame right after the next Update call
    • yield return new WaitForSeconds (3); afaik basically checks once a frame whether the time has already passed and then continues after the next Update call where this is fulfilled
    • and then there are some special YieldInstructions like WaitForEndOfFrame which basically register this IEnumerator in a special queue for a special message event call (EndOfFrame), then continues the execution shifts it back to the normal Coroutine queue (see Event Execution Order)

All these events are executed in the Unity main thread.


And therefore using a Coroutine like you are is quite redundant.

Either simply use

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);
        }
    }
}

or if you prefer doing it in Update without any Coroutine do

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: 
        }
    }
}

or even more simple use InvokeRepeating an 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);
    }
}

So to your questions

Is it the kinds of polling?

No, not really. You don't have to worry at all about synchronization since Unity already does this for you. Internally of course it might be considered "the kind of polling" since you check once a frame if the time has already passed (however, it is also possible that this is handled totally different under the hood - we don't have the source code). "The kind of polling" probably refers to things like

while(!someCondition)
{
    Thread.Sleep(50);
}

or even worse

while(! someCondition) { }

which should be avoided.

How you did use it of course it was kind of a redundant polling implementation I Update as already said, however, except being quite redundant you wouldn't have to worry about performance.

If I don't want to call every time createBallRegularly(). Is there any way to implement like java thread?

Well, as shown above there are multiple better ways to implement your behavior which don't use that flag polling in Update.

I heard the task is similar with thread, then is it might better to use 'startcoroutine' for better performance.

Again as said, Coroutines are neither Task nor Thread.

The general question when to use Task and when Thread is tricky. As a thumbrule I would claim consider Task to be something that is usually expected to take only a short time or be a "one time thing". A Thread on the other hand should be preferred if something is supposed to run for a longer time .. e.g. some worker thread that regularly receives work packages. Both have their pros and cons.

  • Related