Вот что я не получаю: вы говорите, максимум 50 соединений, но только 8 потоков. Каждое соединение по определению «занимает» / работает в потоке. Я имею в виду, что вы не используете DMA или любую другую магию, чтобы снять нагрузку с процессора, поэтому для каждой передачи необходим контекст выполнения. Если вы можете запустить 50 асинхронных запросов одновременно, прекрасно, отлично, сделайте это - вы должны иметь возможность запускать их все из одного потока, так как вызов функции асинхронного чтения по сути не занимает много времени. Если вы, например, имеет 8 ядер и хочет убедиться, что для каждой передачи выделено целое ядро (это, вероятно, глупо, но это ваш код, так что ...), вы можете запустить только 8 передач одновременно.
Мое предложение состоит в том, чтобы просто запустить 50 асинхронных запросов внутри блока синхронизации, чтобы они все запускались до того, как вы позволите любому из них завершить (упрощает математику). Затем используйте семафор подсчета, предложенный Джереми, или синхронизированную очередь, предложенную mbeckish, чтобы отслеживать оставшуюся работу. В конце вашего асинхронного обратного вызова запустите следующее соединение (при необходимости). То есть, запустите 50 соединений, затем, когда закончите, используйте обработчик событий «завершено», чтобы запустить следующее, пока вся работа не будет завершена. Для этого не нужно никаких дополнительных библиотек или фреймворков.