Ты делаешь так: Послал в Tx.
Жду.
Пришло в Rx. {или таймаут}
Goto начало.
А можно так:
В MainLoop или отдельных "потоках" (никто не мешает и свою кооперативную квази-многозадачность запилить), дергаются по-очереди самые разные внутренние функциональные потроха. Тысячи их. Каждый из квазипотоков-объектов-потрохов что-то может слать во внешний мир, формируя датапакеты и записывая в кольцевой буфер передачи. Иногда потоко-функцио-потроха переходят во внутреннее состояние "жду пакета из внешнего мира".
(Пакеты помечаются, кстати. Кому от кого и номер.
Кольцевой Tx буфер по прерываниям постоянно выгребается и сыпется в Tx на передачу
В это же время прерывание ПАРАЛЛЕЛЬНО принимает поток сообщений из внешнего мира в кольцевой Rx буфер.
[Кольцевой Rx буфер выгребается изнутри MainLoop, и если пакет принят, по заголовку анализируется что это такое. Кому это приехало и от кого. И что с этим делать.
Вызывается нужный конкретный получатель и ему вручается пакет. Вызывается нужная реакция или что то же самое Event.
Здесь неявно существует MessageQueue (в виде автомата выгребающего из приемного кольцевого Rx-буфера)
Подобный паттерн/модель это "Cooperative Multitasking" + "Message Passing" + "Message Queue" + "Event Driven".
Работает всё асинхронно, порядок пакетов не важен, они могут приходить совсем в другом порядке, задерживаться, итд.
Канал передачи Rx-Tx занят постоянно, full duplex.
Машина обработки тоже занята постоянно, highload.
Но самое главное, что это прекрасно масштабируется, параллелится. Это ключ к как угодно расширяемой системе (ThreadPool например)
Кажется что это сложность какая-то. Но это нормальный взгляд на систему, потому что при активном обмене из велосипедика послал-жду-принял-gotoначало ничего сделать не удастся.