Por que minha tarefa não é cancelada?

Estou executando um processo que periodicamente se curva a uma lista de URLs para testar o desempenho desses sites. Meu programa principal é basicamente um loop do ... sleep ... while que chama uma função, runTest () a cada N segundos, com uma interrupção do teclado para eliminá-lo. Analisado o básico, meu código é o seguinte:

public void runTest()
{
   if(isRunning)
   {
      return; //Plus some logging that one or more of the tests hasn't completed
   }
   try {
       isRunning = true;
       Curl.GlobalInit((int)CURLinitFlag.CURL_GLOBAL_ALL);
       CancellationTokenSource cts = new CancellationTokenSource(httpTimeoutValueMs * 2);
       foreach (var url in urls)
            taskList.Add(doAsyncCurls(url, cts))
       List<CurlResults> listOfResults = Task.WhenAll(taskList.Select(x => x)).Result.ToList();
       taskList.Clear();
  }
  catch {/*Some exception handling code*/}
  finally { 
    isRunning = false; 
    Curl.GlobalCleanup();
  }
}
private static async Task<CurlResults> doAsyncCurls(string url, CancellationTokenSource cts)
{
    try
    {
      /*Initiate a Curl using libCurl*/
      Easy easy = new Easy();
      easy.SetOpt(CURLoption.CURLOPT_URL, url);
      easy.SetOpt(CURLoption.CURLOPT_DNS_CACHE_TIMEOUT, 0); //Disables DNS cache
      easy.SetOpt(CURLoption.CURLOPT_CONNECTTIMEOUT, httpTimeoutValueMs / 1000); //20sec timeout

      Task t = new Task(() => easy.Perform(), cts.Token);
      t.Start();
      await t;
      return new CurlResults(/*valid results parameters*/);
    } 
    catch (TaskCanceledException)
    { /*Cancellation token invoked*/
        return new CurlResults(/*Invalid results parameters*/);
    }
    catch (Exception e)
    { /*Other exception handling*/ 
        return new CurlResults(/*Invalid results parameters*/);
    }

A função "doAsyncCurl" faz o que diz na lata, configurando um valor de tempo limite http para a metade do token de cancelamento, para que a solicitação HTTP evapore antes que o token de cancelamento seja chamado e gere um resultado negativo. Adicionei o token de cancelamento para tentar resolver o meu problema, conforme abaixo.

Deixo isso em execução há séculos - para começar, tudo funciona bem, mas eventualmente (centenas, se não mais iterações, pela invocação periódica runTest ()) parece que uma das tarefas fica presa e o token de cancelamento não é invocado, e o processo de teste de ondulação está efetivamente preso.

Além de escolher quais URLs são problemáticos (e todos são válidos), o que pode ser feito para diagnosticar o que está errado? Ou construí meus cachos paralelos completamente errados para começar? (Compreendo que eu poderia instanciar novos cachos para URLs que terminaram na rodada anterior e deixar o pendurado pendurado, em vez de esperar que todos terminem antes de iniciar um novo lote, mas não estou preocupado com essa eficiência ganho).

questionAnswers(1)

yourAnswerToTheQuestion