在下面的程序中,我希望该任务得到 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 持有对 continuation 的引用,后者持有对第一个任务的引用。
更新
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);
}