【发布时间】:2013-10-18 09:42:39
【问题描述】:
在下面的程序中,我希望该任务会被 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);
}
【问题讨论】:
-
调试/发布模式?
-
由GC完成时肯定还没有完成的任务复合。
-
汉斯,不是。我正在使用弱参考。还有一个 WaitOne()。在尝试 GC 之前打印任务状态并出现故障。阅读代码:)
-
我刚刚检查过...没有调用任务终结器...这很有趣。
标签: c# task-parallel-library .net-4.5