Про многопоточность и асинхронностьНаткнулся с утра на заметку про многопоточность. Сама заметка мне не особо понравилась. Наверное в основном что там написано "для сеньоров". А для сеньоров она ни о чём, так что ссылку давать не буду. И тезисы из прилегающей к ней подборки статей — тоже. Напишу сам на ту же тему (не для сеньоров).
Многопоточность и асинхронность скажем многие путают.
Простым языком
многопоточность нужна в задачах, которые в основном дают нагрузку на CPU. С планом на эксплуатацию в системах где у процессора много ядер (почти любая сейчас). Из плюсов, что в данном случае мы используем процессор максимально эффективно. Поэтому это требуется в CPU-bound задачах. В играх это у нас была бы физика (но есть PhysX), ИИ, генерация мира и иногда игровая логика.
Асинхронность — это последовательное выполнение операций. Оно работает в одном потоке (в контексте юнити логичнее всего что в главном). Асинхронность по сути переключается между задачами без блокировок не создавая новые события, а используя event loop. Чаще всего в Unity это корутины. Если вдруг вы по какой-то причине не знаете как устроен event-loop в Unity
то вот ссылка. Как это работает не блокируя поток в корутинах? Создаётся список (а если быть точным итератор) операций, который исполняет операции разбитые yield-ами по ходу исполнения Unity Script lifecycle.
Многопоточность штука удобная. Но к сожалению не доступная из коробки шарповыми средствами для скажем WebGL проектов. Шарповый System.Threading и Unity Job System. Тем не менее в современном вебе есть Web Workers. Они не интегрированы в Unity, так как не поддерживают синхронизацию с главным потоком Unity и с DOM. И всё же через нативные js плагины их можно запускать. Если кому-то интересно, то могу как всегда за 🔥 написать пример использования Web Workers из Unity. На десктопе и мобилках всё работает хорошо. Нужно просто знать как избегать deadlock и race condition. Я обычно использовал её для всяких алгоритмах на графах. У меня был проект с укладкой графа в трёхмерном пространстве и вот она считалась в отдельном потоке.
Асинхронность тоже. Но по сути её есть два вида. Так как корутины по-человечески не умеют возвращать результат исполнения той самой корутины, то часто (да и сам я так пишу) используются коллбэки. У них много недостатков.
1. Ад коллбэков Глубоко вложенные коллбеки делают код нечитаемым и сложным для поддержки. Советую почитать про термин "Pyramid of doom" в программировании. И нет, вы что, программисты не любят драматизировать.
2. Сложность обработки ошибокЛомаются трейсы, ломаются логи, нет единого места для трай кетча. Это всегда доставляет неудобства.
3. Инверсия контроляКоллбэки нарушают линейный поток кода, передавая управление внешней функции. Из-за этого сложно отследить как и когда выполнится код.
Ну критикуешь — предлагай. Но всё уже предложили за меня. Мне в целом нравится
UniTask. С ним довольно удобно работать. Тот же async/await подход. Удобный и понятный. Можно конечно пользоваться C# 7.0 async/await, так как в Unity уже давно есть .Net 4.0. Да и UniTask его вроде тоже требует. Так что главное преимущество корутин — это то, что они работают даже с .Net 3.5.
Корутины это что-то вроде урезанного реактивного подхода. При правильной организации классов, там можно как-то жить без коллбэков делая аналоги паттерна DirtyFlag и тому подобное. Но мне запомнилась шутка из одной из лекций майкрософт (не связанная с Unity) — "Давайте напишем async, мы же не животные" (скучаю по временам когда корпорации были не такими беззубыми) :)
#интересное