Корутины в Unity. Что это и как использовать?
Тут стоит уточнить, параллельно не значит асинхронно. То есть, если вы запихнете свой «тяжелый» код в кучу корутин которые будут работать параллельно, программа работать быстрее не будет.
Наглядный пример
Зачем же тогда нужны корутины?
Они помогут нам упростить свой код. К примеру, необходимо плавно менять цвет спрайта.
Можно, конечно, делать это в Update, но корутину можно написать так что-бы она, что-то делала раз в пол секунды или в секунду. И для этого не нужно будет проверять каждый раз сколько времени прошло с последнего вызова Update.
Вот пример, который выведет сообщение спустя пол секунды после его запуска.
А если обернуть в цикл? Он будет выполняться каждые пол секунды.
Замените 0.5f на нужное значение для получения нужной задержки.
Кстати для запуска такой функции-корутины в нужном месте необходимо написать:
Yield, что это?
Эта команда, отдает процессорное время основному потоку и продолжает выполнение корутины с этого места когда произойдет необходимое событие, а событий есть достаточное количество.
1.Продолжить после следующего FixedUpdate
2. Продолжить через некоторое время
3. Продолжить после следующего LateUpdate и рендеринга сцены
4. Продолжить после другой корутины:
5. После текущего Update
6. По завершении скачивания
7. Завершить корутину
Как завершить корутину?
Иногда корутину нужно завершить преждевременно. Для это есть несколько путей.
- StopAllCoroutines()
- StopCoroutine(«YourCoroutine»)
- Уничтожить родительский GameObject
Делаем выводы
Корутины удобны в некоторых случаях и иногда без них совсем туго, поэтому советую вникнуть в их суть. Часто они помогают облегчить код и сэкономить время. И помните, корутины это не потоки!)
Корутины в Python
Когда говорят «написать корутину», обычно подразумевают асинхронную функцию. Корутины можно ставить на паузу, чтобы дать другим функциям немного поработать. В этом заключается принцип асинхронности. О нём мы рассказывали в этой статье.
Давайте сразу рассмотрим пример асинхронной функции:
Очень похоже на обычную функцию, однако здесь есть два новых слова: async и await .
async говорит Питону о том, что мы пишем не просто функцию, а асинхронную функцию. Просто добавили async и всё, функция теперь асинхронная.
Второе слово — await. Оно прерывает исполнение функции, и возвращает управление программой наружу. После этого корутину можно запустить повторно, а затем еще и еще, и каждый раз она будет продолжать работу с того await , на котором прервалась ранее. Например, в функции count_to_three команда await встречается три раза, значит корутину можно вызвать четыре раза (да, не три!). Корутина будет работать до первого await, затем до второго, до третьего и на четвёртый раз выполнит остатки до конца.
Нельзя делать await None или await "Hello, World!" . Можно await только то, что так и называют — «awaitable».
await asyncio.sleep(0) — это команда корутине «Дай поработать другим!»
Сразу покажем, как это выглядит на практике:
Мы вызываем асинхронную функцию count_to_three , однако она не выводит на экран цифру 1, а возвращает корутину. Все асинхронные функции так делают. Это сделано для того, чтобы у вас был объект этой корутины в переменной. Теперь корутину можно запускать раз за разом, а она раз за разом будет делать кусочек и останавливаться на следующем await .
Чтобы запустить корутину, используют метод send() . При каждом запуске корутины этим методом она продолжает исполняться с последнего await , на котором она остановилась. Поэтому при новом запуске той же корутины срабатывает не тот же print , а следующий.
Нельзя просто .send() . Всегда нужно передавать какое-то значение. Об этом тоже расскажем позже. Пока что воспринимайте .send(None) как команду «продолжи выполнять корутину».
Когда корутина закончится?
Она остановится навсегда, когда закончатся все await или встретится return . Когда корутина заканчивается — она истощается и вызов .send() выдаёт ошибку:
Если мы хотим запустить наш счётчик сначала, придётся создать новую корутину, вызвав count_to_three() :
Обычно заранее не известно сколько await будет до момента «истощения», поэтому исключение приходится «перехватывать»:
Исключение StopIteration возникает всего один раз. Если после него попробовать запустить корутину ещё раз, то поднимется другое исключение — RuntimeError , и оно уже будет считаться ошибкой. О том как работать с исключениями читайте в статье про try except.
Нельзя запускать истощённую корутину.
Добиваемся асинхронности
С корутинами разобрались, останавливать их научились. А зачем.
Корутины позволят вашему коду работать асинхронно, т.е. делать несколько вещей одновременно. Допустим, вы решили скачать несколько файлов. Обычный, синхронный код скачивает файлы по-очереди. Сначала первый файл целиком, затем второй, тоже целиком. Асинхронный код качает файлы одновременно, по кусочкам. Приведём пример скачивания двух файлов:
Разберём как работает код:
Мы создали 2 корутины: image_downloader и music_downloader . Первая качает картинку по ссылке https://www.some-images.com/image1.jpg , вторая — музыку по ссыке https://www.music-site.com/artist/album/song5.mp3 .
Мы положили их в список coroutines
В бесконечном цикле мы по очереди запускаем все корутины из списка. Если вышла ошибка StopIteration — корутина истощилась, т.е. файл скачан. Убираем её из списка, корутина больше запускаться не будет.
Чтобы итерация по списку coroutines не сбивалась после удаления элемента из него итерируем не по оригиналу, а по копии coroutines.copy() .
Если список с корутинами закончился (его длина равна нулю), пора заканчивать и бесконечный цикл, потому что все файлы скачаны.
Передать параметры в асинхронную функцию
В плане аргументов асинхронные функции ничем не отличаются от обычных. Доработаем пример со счетчиком и вместо async def count_to_three напишем универсальную функцию async def count :
Попробуйте бесплатные уроки по Python
Получите крутое код-ревью от практикующих программистов с разбором ошибок и рекомендациями, на что обратить внимание — бесплатно.
Coroutine
Coroutine (корутины), или сопрограммы — это блоки кода, которые работают асинхронно, то есть по очереди. В нужный момент исполнение такого блока приостанавливается с сохранением всех его свойств, чтобы запустился другой код. Когда управление возвращается к первому блоку, он продолжает работу. В результате программа выполняет несколько функций одновременно.
Для чего нужны корутины
- Для создания асинхронных приложений, которые могут выполнять несколько действий одновременно.
- Для гибкой и удобной реализации многозадачности.
- Для большего контроля при переключении между разными задачами. Корутинами управляют разработчик и программа, а не операционная система.
- Для снижения нагрузки на аппаратные ресурсы устройства.
Корутины поддерживают Kotlin, JavaScript, PHP, C#, Go, Python, Ruby, Lua и другие языки программирования.
Как работают сопрограммы
Сопрограммы — это потоки исполнения кода, которые организуются поверх системных потоков. Поток исполнения кода — это последовательность операций, выполняющихся друг за другом. Она исполняется, пока не наступит момент, который задан в коде или определен разработчиком. Затем может начать выполняться часть другой последовательности операций. В системных потоках содержатся инструкции процессора, на одном его ядре могут по очереди работать разные системные потоки. Корутины работают на более высоком уровне: несколько сопрограмм могут поочередно выполнять свой код на одном системном потоке.
Для конечного пользователя работа корутин выглядит как несколько действий, которые выполняются параллельно.
Принципы работы корутин:
- Могут иметь несколько точек для входа и возврата.
- Приостанавливаются и возобновляют работу больше одного раза.
- Действуют по стратегии LIFO — последняя вызванная корутина закончится первой.
Различия между корутинами и потоками
Многозадачность с использованием корутин легко перепутать с многопоточностью. Это выполнение программы в нескольких системных потоках.
Поток — составная часть процесса, который выполняется в операционной системе. Принцип похож: несколько потоков останавливаются и возобновляются, ждут друг друга, общаются. Но есть отличия.
- Потоками управляет операционная система. Переключением корутин — разработчик с помощью кода.
- Переключение потоков сложно контролировать. Корутины контролируются более гибко.
- Потоки отнимают много ресурсов процессора — ему постоянно приходится переключаться между ними. Корутины не требуют переключения контекста, поэтому код потребляет мало ресурсов.
- Потоки выполняются на аппаратном или системном уровне. Корутины — более высокоуровневое решение. Это значит, что они дальше от системы и аппаратных ресурсов, зато ближе к человеческим понятиям.
- Потоки ускоряют выполнение сложной задачи, но отнимают много ресурсов. Корутины не повышают скорость, но помогают оптимизировать нагрузку.
- Корутины выполняются в рамках одного потока или пула потоков.
Преимущества использования корутин
Эффективность
Многозадачная программа эффективнее расходует ресурсы. Основной код не блокируется, чтобы мог выполниться вспомогательный модуль. Вместо этого они работают асинхронно.
Удобство для пользователя
Корутины переключаются быстро, для пользователя это выглядит как одновременное выполнение задач. Ему не приходится подолгу ждать ответа программы. Он продолжает работать с ней, пока асинхронные корутины незаметно для него выполняют дополнительные действия.
Снижение нагрузки на систему
Асинхронность позволяет выполнять несколько действий в рамках одного потока вместо того, чтобы множить потоки. Системе проще выполнить один поток, чем несколько. Разработчики из JetBrains прояснили это на таком примере: запустить 10 тысяч корутин одновременно — задача, с которой устройство справится. А 10 тысяч потоков — невозможное явление: компьютеру не хватит памяти.
Гибкость в управлении
Переключение между корутинами происходит вручную. Программист сам прописывает этот момент в коде. Поэтому управление корутинами можно контролировать, что дает разработчику больше возможностей. Нет риска, что программа переключится между блоками кода в неподходящий момент, как это бывает с менее гибкими решениями.
Недостатки сопрограмм
Сложность и высокий порог вхождения
Разобраться в работе корутин и научиться писать асинхронный код может быть сложно. Поэтому сопрограммы начинают изучать специалисты, которые уже хорошо разобрались в базовых принципах выбранного языка.
Узкая специализация
Корутины — высокоуровневое решение. Ускорить сложные вычисления с помощью сопрограмм не получится. В этом случае нужна многопоточность, а не асинхронность. При этом корутины подходят для снижения нагрузки на систему.
Отсутствие в ряде языков
Корутины трудно реализовать в языках, которые их не поддерживают. Для этого нужно использовать сторонние решения или преобразовывать код на одном языке в другой. Это усложняет разработку.