Tarea no recogida de basura

En el siguiente programa, esperaría que la tarea obtuviera GC'd, pero no es así. He utilizado un perfilador de memoria que mostró que elCancellationTokenSource contiene una referencia a él, aunque la tarea está claramente en un estado final. Si me quitoTaskContinuationOptions.OnlyOnRanToCompletion, todo funciona como se espera.

¿Por qué sucede y qué puedo hacer para prevenirlo?

    static void Main()
    {
        var cts = new CancellationTokenSource();

        var weakTask = Start(cts);

        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();

        Console.WriteLine(weakTask.IsAlive); // prints True

        GC.KeepAlive(cts);
    }

    private static WeakReference Start(CancellationTokenSource cts)
    {
        var task = Task.Factory.StartNew(() => { throw new Exception(); });
        var cont = task.ContinueWith(t => { }, cts.Token, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Default);
        ((IAsyncResult)cont).AsyncWaitHandle.WaitOne(); // prevents inlining of Task.Wait()
        Console.WriteLine(task.Status); // Faulted
        Console.WriteLine(cont.Status); // Canceled
        return new WeakReference(task);
    }

Mi sospecha es que debido a que la continuación nunca se ejecuta (no cumple con los criterios especificados en sus opciones), nunca se anula el registro del token de cancelación. Así que el CTS contiene una referencia a la continuación, que contiene una referencia a la primera tarea.

Actualizar

El equipo de PFX ha confirmado que esto parece ser una fuga. Como solución alternativa, hemos dejado de usar cualquier condición de continuación cuando usamos tokens de cancelación. En su lugar, siempre ejecutamos la continuación, verificamos la condición en el interior y lanzamos unaOperationCanceledException Si no se cumple. Esto preserva la semántica de la continuación. El siguiente método de extensión encapsula esto:

public static Task ContinueWith(this Task task, Func<TaskStatus, bool> predicate, 
    Action<Task> continuation, CancellationToken token)
{
    return task.ContinueWith(t =>
      {
         if (predicate(t.Status))
              continuation(t);
         else
              throw new OperationCanceledException();
      }, token);
}

Respuestas a la pregunta(2)

Su respuesta a la pregunta