Является ли coroutine новой нитью в Unity3D?

Я смущен и интересуюсь тем, как сопрограммы (в Unity3D и, возможно, в других местах). Является ли coroutine новой нитью? Unity documentation они сказали:

Корутин - это функция, которая может приостановить ее выполнение (выход) до тех пор, пока не будет завершена заданная YieldInstruction.

И у них есть примеры С# здесь:

using UnityEngine;
using System.Collections;

public class example : MonoBehaviour {
    void Start() {
        print("Starting " + Time.time);
        StartCoroutine(WaitAndPrint(2.0F));
        print("Before WaitAndPrint Finishes " + Time.time);
    }
    IEnumerator WaitAndPrint(float waitTime) {
        yield return new WaitForSeconds(waitTime);
        print("WaitAndPrint " + Time.time);
    }
}

У меня есть много вопросов по этому примеру:

  • В приведенном выше примере, какая строка является сопрограммой? Является ли WaitAndPrint() сопрограммой? Является ли WaitForSeconds() сопрограммой?

  • В этой строке: yield return new WaitForSeconds(waitTime);, почему присутствуют как yield, так и return? Я читал в Документация Unity, что "Оператор yield - это особый вид возврата, который гарантирует, что функция будет продолжена из строки после оператора yield в следующий раз он называется". Если yield является специальным return, что здесь делает return?

  • Зачем нам возвращать IEnumerator?

  • Создает ли StartCoroutine новый поток?

  • Сколько раз в предыдущем примере вызывается WaitAndPrint()? Действительно ли yield return new WaitForSeconds(waitTime); вернулся? Если да, то, думаю, WaitAndPrint() вызывается дважды в приведенном выше коде. И я думаю, StartCoroutine() вызывал WaitAndPrint() несколько раз. Тем не менее, я увидел еще одну документацию Unity, в которой говорится: "Выполнение сопрограммы можно приостановить в любой момент с помощью инструкции yield. указывает, когда сопрограмма возобновлена ​​". Эти слова заставляют меня чувствовать, что WaitAndPrint() на самом деле не вернулся; это было просто приостановлено; он ожидал возвращения WaitForSeconds(). Если это так, то в приведенном выше коде WaitAndPrint() был вызван только один раз, а StartCoroutine просто отвечал за запуск функции, а не вызывал ее несколько раз.

Ответ 1

Coroutines - чрезвычайно мощный метод, используемый для эмуляции типов функций, поддерживаемых async/await в .net4.5, но в более ранних версиях (С# >= v2.0).

Microsoft CCR (прочитайте) также использует (использует?) этот подход.

Позвольте получить одну вещь. yield один недействителен и всегда следует либо return, либо break.

Подумайте о стандартном IEnumerator (который не дает сообщений управления потоком).

IEnumerator YieldMeSomeStuff()
{
    yield "hello";
    Console.WriteLine("foo!");
    yield "world";
}

Сейчас:

IEnumerator e = YieldMeSomeStuff();
while(e.MoveNext())
{
    Console.WriteLine(e.Current);
}

Какой результат?

hello
foo!
world

Обратите внимание, что во второй раз, когда мы вызывали MoveNext, до того, как Enumerator дал "мир", в Enumerator пробежал некоторый код. Это означает, что в Enumerator мы можем написать код, который будет выполняться до тех пор, пока он не достигнет инструкции yield return, а затем просто остановится, пока кто-то не назовет MoveNext (удобно, когда все состояния/переменные аккуратно захвачены, поэтому мы можем подобрать, где мы прекращено). После вызова MoveNext следующий бит кода после оператора yield return может работать до тех пор, пока не будет достигнут другой yield return. Таким образом, теперь мы можем контролировать выполнение кода между операторами yield return с вызовом MoveNext в Enumerator.

Теперь, скажем, вместо того, чтобы уступать строки, наш Enumerator должен был выдать сообщение, которое говорит вызывающему абоненту MoveNext, "пожалуйста, обернитесь для x (waitTime) секунд, прежде чем вы снова вызовете MoveNext". Вызывающий звонит, чтобы "понять" множество сообщений. Эти сообщения всегда будут соответствовать следующим образом: "Подождите, пока что-то произойдет, прежде чем вызывать MoveNext снова".

Теперь у нас есть мощное средство приостановки и перезапуска кода, требующего выполнения других условий, прежде чем он сможет продолжить, без необходимости писать эту функциональность в другой метод, например, делать асинхронные вещи без сопрограмм. Без сопроцессоров вы должны пройти вокруг ужасного объекта состояния aync, который вам нужно будет вручную собрать, чтобы зафиксировать состояние между концом одного метода и началом другого после некоторого асинхронного материала). Coroutines устраняет это, потому что область сохраняется (с помощью магии компилятора), поэтому ваши локальные переменные сохраняются в течение длительного времени, асинхронные вещи.

StartCoroutine просто запускает весь процесс. Он вызывает MoveNext в Enumerator... в Enumerator запускается некоторый код... Перечислитель выводит управляющее сообщение, которое сообщает код в StartCoroutine, когда вызывать MoveNext снова. Этого не должно быть в новом потоке, но оно может быть полезно в многопоточных сценариях, потому что мы можем вызывать MoveNext из разных потоков и контролировать, где выполняется работа.