Zadanie nie jest zbierane

W poniższym programie oczekuję, że zadanie uzyska GC'd, ale tak nie jest. Użyłem profilera pamięci, który pokazał, żeCancellationTokenSource zawiera odniesienie do niego, mimo że zadanie jest wyraźnie w stanie ostatecznym. Jeśli usunęTaskContinuationOptions.OnlyOnRanToCompletion, wszystko działa zgodnie z oczekiwaniami.

Dlaczego tak się dzieje i co mogę zrobić, aby temu zapobiec?

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

Podejrzewam, że ponieważ kontynuacja nigdy nie działa (nie spełnia kryteriów określonych w opcjach), nigdy nie wyrejestrowuje się z tokena anulowania. Tak więc CTS zawiera odniesienie do kontynuacji, która zawiera odniesienie do pierwszego zadania.

Aktualizacja

Zespół PFX potwierdził, że jest to przeciek. Aby obejść ten problem, przestaliśmy używać wszelkich warunków kontynuacji podczas korzystania z tokenów anulowania. Zamiast tego zawsze wykonujemy kontynuację, sprawdzamy stan wewnątrz i rzucamyOperationCanceledException jeśli nie zostanie spełniony. Zachowuje to semantykę kontynuacji. Następująca metoda rozszerzenia obejmuje to:

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

questionAnswers(2)

yourAnswerToTheQuestion