Qual é o equivalente assíncrono / espera de um servidor ThreadPool?

Estou trabalhando em um servidor tcp que se parece com isso usando APIs síncronas e o pool de threads:

TcpListener listener;
void Serve(){
  while(true){
    var client = listener.AcceptTcpClient();
    ThreadPool.QueueUserWorkItem(this.HandleConnection, client);
    //Or alternatively new Thread(HandleConnection).Start(client)
  }
}

Supondo que meu objetivo é lidar com o maior número possível de conexões simultâneas com o menor uso de recursos, isso parece que será rapidamente limitado pelo número de threads disponíveis. Suspeito que, usando as APIs de tarefas sem bloqueio, seja capaz de lidar com muito mais com menos recursos.

Minha impressão inicial é algo como:

async Task Serve(){
  while(true){
    var client = await listener.AcceptTcpClientAsync();
    HandleConnectionAsync(client); //fire and forget?
  }
}

Mas me parece que isso pode causar gargalos. Talvez o HandleConnectionAsync demore um tempo extraordinariamente longo para atingir a primeira espera e interrompa o processo de aceitação principal. Isso usará apenas um encadeamento de sempre ou o tempo de execução executará magicamente as coisas em vários encadeamentos, conforme entender?

Existe uma maneira de combinar essas duas abordagens para que meu servidor use exatamente o número de threads necessário para o número de tarefas em execução ativa, mas para não bloquear threads desnecessariamente nas operações de E / S?

Existe uma maneira idiomática de maximizar o rendimento em uma situação como essa?

questionAnswers(5)

yourAnswerToTheQuestion