Я полагаю, что вы делаете это так же, как любая другая асинхронная операция в .NET: вы вызываете версию метода BeginXxx, в данном случае BeginAcceptSocket. Ваш обратный вызов будет выполнен в пуле потоков.
Пулы, как правило, масштабируются намного лучше, чем потоки на соединение: как только вы получите несколько десятков соединений, система будет работать намного тяжелее при переключении между потоками, чем при фактической работе. Кроме того, каждый поток имеет свой собственный стек, размер которого обычно составляет 1 МБ (хотя это зависит от флагов канала), который должен быть найден в виртуальном адресном пространстве 2 ГБ (в 32-разрядных системах); на практике это ограничивает вас до 1000 потоков.
Я не уверен, использует ли он сейчас пул потоков .NET, но в Windows есть объект ядра, называемый портом завершения ввода / вывода, который помогает в масштабируемом вводе / выводе. Вы можете связать потоки с этим объектом, и с ним могут быть связаны запросы ввода-вывода (включая прием входящих соединений). Когда ввод-вывод завершается (например, устанавливается соединение), Windows освобождает ожидающий поток, но только в том случае, если количество выполняемых в данный момент потоков (не заблокированных по какой-либо другой причине) меньше настроенного предела масштабируемости для порта завершения. Обычно вы устанавливаете это на кратное число ядер.