¿Por qué mi tarea no se cancela?

Estoy ejecutando un proceso que periódicamente se curva en una lista de URL para probar el rendimiento de esos sitios web. Mi programa principal es básicamente un bucle do ... sleep ... while que llama a una función, runTest () cada N segundos, con una interrupción del teclado para matarlo. Teniendo en cuenta lo básico, mi código es el siguiente:

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*/);
    }

La función "doAsyncCurl" hace lo que dice en la lata, configurando un valor de tiempo de espera http a la mitad del token de cancelación, de modo que la solicitud HTTP debería e, evaporarse antes de que se invoque el token de cancelación, y generar un resultado negativo. Agregué el token de cancelación para intentar solucionar mi problema como se muestra a continuación.

Dejo esto funcionando durante siglos: para empezar, todo funciona bien, pero eventualmente (cientos, si no más iteraciones a través de la invocación periódica runTest ()) parece que una de las tareas se atasca y no se invoca el token de cancelación, y el proceso de prueba de rizos está efectivamente atascado.

Además de elegir qué URL (s) son problemáticas (y todas son válidas), ¿qué se puede hacer para diagnosticar qué está pasando? ¿O he construido mis rizos paralelos completamente erróneamente para empezar? (Aprecio que podría crear instancias de nuevos Rizos en las URL que han terminado de la ronda anterior y dejar que cuelgue el colgado, en lugar de esperar a que todos terminen antes de lanzar un nuevo lote, pero no me preocupa esa eficiencia ganancia).