CancellationToken泄漏内存

时间:2018-08-01 21:02:52

标签: c# asynchronous cancellationtokensource

我有一个计时器,每2秒启动2个任务...我在一个简单的列表中跟踪这些任务,(以便我在停止应用程序时可以等待它们完成)。

任务有效地进入数据库,运行几次更新并完成。
任务本身不会花费超过一秒钟的时间

...
// global variables to keep track of the running tasks.
List<Task> _tasks = new List<Task>();
CancellationTokenSource _cts = new CancellationTokenSource();

// in the timer function
private void Timer(object sender, ElapsedEventArgs e)
{
  _tasks.Add( Foo1Async(_cts.Token) );
  _tasks.Add( Foo2Async(_cts.Token) );
  // remove the completed ones
  _tasks.RemoveAll(t => t.IsCompleted);
}
...

几分钟后,内存从40Mb上升到130Mb(并不断攀升)...

如果我仅替换 下面的代码和,并且没有其他内容

...
  _tasks.Add( Foo1Async( CancellationToken.None)) );
  _tasks.Add( Foo2Async( CancellationToken.None)) );
...

内存保持稳定在40Mb,并且永远不会增加。

几点

  • 在测试过程中,我不会取消任何任务(它们全部运行完毕)。
  • 我在考试期间不会抛出任何异常。
  • 我使用的数据库是Sqlite
  • 在两次测试期间,我所做的工作都完全相同,唯一的区别是所使用的CancellationToken。
  • 即使我等了很长时间,记忆也似乎从未被释放。
  • 计时器是System.Timers.Timer,我只在处理计时器事件后才重新启动它。
  • 我不使用任何其他代币(或代币来源)
  • 我不注册任何令牌取消。

如果我拍摄内存快照,CancellationCallbackInfo的数量似乎是问题所在,但我不知道它们来自何处以及如何释放它们。

看看.NET源代码,在CancellationTokenSource中使用了回调,但我不确定如何向其中添加回调(或如何释放它们)。

关于使用_cts.Token和使用CancellationToken.None时可能导致内存泄漏的任何建议

1 个答案:

答案 0 :(得分:3)

.NET DBCommand的某些代码中存在/存在问题,异步函数注册为取消,但只有在出现异常时才进行处理。如您所见,在大多数情况下,它只会增加列表。

此问题已在last year in .NET core v2.x某个时间修复,(不确定是否会在.NET标准中修复,我使用的是4.6.1,但这仍然是一个问题)。

解决该问题的一种方法是围绕所需的异步函数编写自己的包装器。

您还可以调用非异步功能,(但您会丢失异步功能为您提供的“取消”功能)。