简体   繁体   中英

Trying to understand c# yield in Unity3D

I've got a class. It has a method that does a lot of work. I would like to not have the program hang while it does the work. I understand yield will do this for me.

void Start() {
  DoWork(10,10);
}

void DoWork (int x, int y) {
  for (int i=0; i < x; i++) {
    for (int j=0; j < y; j++) {
      // Stuff
    }
  }
}

If I add a yield like this

void Start() {
  DoWork(10, 10);
}

IEnumerable DoWork (int x, int y) {
  for (int i=0; i < x; i++) {
    for (int j=0; j < y; j++) {
      // Stuff
    }
    Debug.Log (1);
    yield return null;
  }
}

None of the work gets done and on top of that I see no log statements at all.

How do I yield my code so the program doesn't freeze?

You need to use the StartCoroutine method:

void Start() {
  StartCoroutine(DoWork(10, 10));
}

IEnumerator DoWork (int x, int y) {
  // (A)
  yield return null;
  // (B)
  for (int i=0; i < x; i++) {
    for (int j=0; j < y; j++) {
      // Stuff
    }
    Debug.Log (1);
    yield return null;
    // (C)
  }
}

Yur code is executed piece by piece where delimiter of steps is the yield operator, ie when Framework calls MoveNext() the first time - the code (A) will be executed, when it calls MoveNext() second time - the code (B) will be executed, then code (C), and so on and so forth.

This is Unity3D engine so your coroutine needs to return IEnumerator to be valid:

void Start() {
  StartCoroutine(DoWork(10, 10));
}

IEnumerator DoWork (int x, int y) {
  for (int i=0; i < x; i++) {
    for (int j=0; j < y; j++) {
      // Stuff
    }
    Debug.Log (1);
    yield return null;
  }
}

This is in no way multithreading. It is run just like an update once per frame between the Update and the LateUpdate except if you use

 yield return new WaitForEndOfFrame();

then it is postponed until after the rendering process. What it does is create a new object of type Coroutine and place it on the calling MonoBehaviour stack of coroutines.

This works as a method that performs some repetitive action but always return to the main program when hitting a yield. It will then catch back from there on the next frame.

When you add a yield statement, the compiler actually generates a private class that acts as a state machine that implements IEnumerable . As such none of the code wrapped up from the original method will be called unless you enumerate the result of the method - in your example, you're throwing away the return value, so nothing would happen.

Yield keyword is used for lazy loading/computation support in C#.

Try doing:

var result = DoWork().ToList();

This forces an evaluation of the DoWork() method and you will see the logging taking place.

C# yield in Unity works just like C# yield always does. Unity does not influence this in any way.

yield is a keyword that is used to allow enumeration over a set of return values.

IEnumerator<int> MyEnumerationMethod()
{
    yield return 5;
    yield return 1;
    yield return 9;
    yield return 4;
}

void UserMethod1()
{
    foreach (int retVal in MyEnumerationMethod())
        Console.Write(retVal + ", ");

    // this does print out 5, 1, 9, 4, 
}

void UserMethod2()
{
    IEnumerator<int> myEnumerator = MyEnumerationMethod();
    while (myEnumerator.MoveNext())
        Console.Write(myEnumerator.Current + ", ");

    // this does print out 5, 1, 9, 4, 
}

UserMethod1() and UserMethod2() are pretty much the same. UserMethod1() is just the C# syntactic sugar version of UserMethod2().

Unity uses this language feature to implement Coroutines:

When you call StartCoroutine() and pass it an IEnumerator , Unity stores this enumerator and calls MoveNext() for the first time. This will cause MyEnumerationMethod() to be called and executed up until the first yield return . At this point, MoveNext() returns and the first result (5) can be retrieved by looking at the Current property of the enumerator.

Now, Unity regularly checks the Current property and - depending on its value - decides whether the time has come to call MoveNext() again. The value of Current might be an instance of WaitForEndOfFrame , an instance of WWW or whatever, and depending on that the time, MoveNext() is called is decided.

Once MoveNext() is called again, execution of MyEnumerationMethod() will be continued at the point where it was interrupted last time, and executes until the next yield return is executed. And so on.

That's all there is to yield, and to Coroutines in Unity.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM