[EXP] Unity 3d - Отложенные действия. Invoke VS Coroutines.

Довольно часто в играх существует необходимость выполнять какие-либо действия отложенным образом, в том числе и по времени. Для этого в Unity 3d предусмотрены методы:

• Invoke(string methodName, float time);

• InvokeRepeating(string Method Name, float time, float repeatRate);

• CancelInvoke(); и CancelInvoke(string methodName);

Invoke полагается на Time.timeScale. То есть, если мы вызываем Invoke("SomeMethod", 2f) при Time.timeScale = 0.5, то SomeMethod() начнет выполняться через 4 секунды, а при 0 - не начнет выполняться вовсе.

Не одним Invoke-ом сыты, ведь есть еще способы отложено выполнить действия, например таймеры или корутины. Вот на них, я пожалуй и остановлюсь.

Корутины в данном контексте мне всегда казались очень производительными, практичными и более знакомыми с точки зрения: "А что под капотом?", но не столь удобными в использовании, как тот же Invoke, ведь приходится жертвовать парой строчек кода... А ещё, корутины решают проблему с отложенным запуском при различных значениях Time.timeScale.

Вот как выглядит отложенный запуск с использованием Coroutines:

float _time = 2f; Coroutine _coroutine; private void Start() = > _coroutine = Start(SomeCoroutine()); public IEnumerator SomeCoroutine() { yield return new WaitForSeconds(_time); //Ожидание в условиях Time.timeScale //TO DO 1 yield return new WaitForSeconds(_time); //TO DO 2 yield return new WaitForSecondsRealtime(_time); //Ожидание в реальном времени //TO DO 3 и таким образом можно создать цепочку действий } public void StopMyCoroutine() => StopCoroutine(_coroutine); //Останавливает указанную корутину public void StopAllMyCoroutines() => StopAllCoroutines(); //Останавливает все корутины

Вот так может выглядеть использование Invoke:

float _time = 2f; private void Start() => Invoke(nameof(SomeMethod), _time); private void SomeMethod() { //TO DO 1 Invoke(nameof(SomeMethod2), _time); //типо цепочка действий } private void SomeMethod2() { //TO DO 2 } public void CancelMyInvoke() => CancelInvoke(nameof(SomeMethod)); //отменяет указанный Invoke public void CancelAllInvokes() => CancelInvoke(); //отменяет все Invoke

Но, возвращаясь к изначальной теме, кто круче то? В этом вопросе я учитываю такие критерии как:
• Производительность
• Удобство
• Гибкость

Я думаю, некоторые выводы об удобстве и гибкости сделаны (о них чуть позже). А вот с производительностью дела обстоят по-интереснее...

Тестирование производительности

Для тестирования я не придумал ничего лучше, как создать 5940 сущностей, которые одновременно будут вызывать отложенное действие через Invoke и Coroutine, и наблюдать за этим в профайлере.

Код.
Код.
Армия тёмных сущностей.
Армия тёмных сущностей.
Результат 1.
Результат 1.
Результат 2. WaitForSecondsRealtime в Coroutine.
Результат 2. WaitForSecondsRealtime в Coroutine.

Из тестов можно сделать вывод, что Invoke оказался производительней... А WaitForSecondsRealtime вносит свою лепту в общую картину.

Заключение

Учитывая всё выше сказанное, я склоняюсь к тому, что Invoke более прост в использовании и, как оказывается, производительней, а корутины более многозадачные и гибкие.
Лично мне отдавать предпочтение чему-то одному не приходится - всегда стоит исходить из ситуации и решаемой задачи. Для простых задач можно использовать Invoke, а для более трудных, требующих гибкого подхода можно использовать Coroutine, жертвуя малой каплей производительности.

Надеюсь, статья была вам интересна и полезна. Если у вас есть иной личный опыт использования Invoke и Coroutine, будет интересно узнать что-то новое. Всем мир)

P.S.: Первая статья на DTF. Если у вас есть какие-либо пожелания, оценка или совет формата подачи, я открыт к критике.

44
5 комментариев

Invoke обладает еще не очень удобным моментом, он выполнится даже если объект выключили, мне обычно удобнее предполагать что выключенный объект ничего не будет делать. Но где то может пригодиться обратное.

1
Ответить

Хм, действительно это камень в огород Invoke) Получается, теперь это нужно учитывать в голове. Спасибо за обратную связь!

1
Ответить

Не люблю корутины, их вызов интуитивно менее понятен, чем инвоки, поэтому когда нужно использую Invoke)

1
Ответить

Если ситуация позволяет, почему бы и нет) Спасибо за комментарий!

1
Ответить

Комментарий недоступен

Ответить