在下面的程序中,我希望任务得到垃圾回收,但它没有。我使用了一个内存分析器,显示CancellationTokenSource仍然持有对它的引用,即使任务已经处于最终状态。如果我移除
为什么会发生这种情况,我该怎么做才能防止它?
我的怀疑是,由于连续运行程序从未运行过(它不符合其选项中指定的条件),因此它从未从取消令牌中注销。因此,CTS持有对连续的引用,该引用又持有对第一个任务的引用。
更新
PFX团队已确认这似乎是一个泄漏问题。作为一种解决方法,我们停止使用任何连续条件来使用取消令牌。取而代之的是,我们总是执行连续,检查内部条件,如果不符合,则抛出OperationCanceledException。这保留了连续的语义。以下扩展方法封装了此过程:
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);
}