10

次のプログラムでは、タスクが GC されることを期待しますが、そうではありません。CancellationTokenSourceタスクが明らかに最終状態にあるにもかかわらず、 がそれへの参照を保持していることを示すメモリ プロファイラを使用しました。を削除するTaskContinuationOptions.OnlyOnRanToCompletionと、すべてが期待どおりに機能します。

なぜそれが起こるのか、それを防ぐために何ができるのか?

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

私の疑いでは、継続が実行されない (オプションで指定された基準を満たさない) ため、キャンセル トークンから登録が解除されることはありません。したがって、CTS は、最初のタスクへの参照を保持する継続への参照を保持します。

アップデート

PFX チームは、これがリークであるように見えることを確認しました。回避策として、キャンセル トークンを使用する際の継続条件の使用を停止しました。代わりに、常に継続を実行し、内部の条件をチェックし、OperationCanceledExceptionそれが満たされない場合はスローします。これにより、継続のセマンティクスが保持されます。次の拡張メソッドはこれをカプセル化します。

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

2 に答える 2

-1

追加として...この場合、ファイナライザーが呼び出されています:

WeakReference weakTask = null;
using (var cts = new CancellationTokenSource())
{
  weakTask = Start(cts);
}

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

Console.WriteLine(weakTask.IsAlive); // prints false
于 2013-10-11T07:06:26.647 に答える