Eli*_*bel 10 c# task-parallel-library .net-4.5
在下面的程序中,我希望任务能够获得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);
}
Run Code Online (Sandbox Code Playgroud)
我怀疑是因为延续从不运行(它不符合其选项中指定的标准),所以它永远不会从取消令牌中取消注册.因此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);
}
Run Code Online (Sandbox Code Playgroud)
简短的回答:我认为这是一个内存泄漏(或两个,见下文),你应该报告它.
答案很长:
之所以Task不GCed是因为它是可到达从CTS这样的:cts→交通cont→交通task.我认为这两个引用都不应该存在于你的情况中.
该cts→ cont参考是那里,因为cont正确使用令牌注销登记,但它从来没有注销.它在Task正常完成时取消注册,但在取消时取消注册.我的猜测是错误的逻辑是,如果任务被取消,则无需取消注册,因为必须是取消导致任务被取消.
所述cont→ task参考是存在的,因为cont实际上是ContinuationTaskFromResultTask(从派生的类Task).这个类有一个包含先行任务的字段,当成功执行延续时,该字段被清空,但是当它被取消时则不被清除.
| 归档时间: |
|
| 查看次数: |
1509 次 |
| 最近记录: |