由于“直接委托根”导致System.Timers.Timer泄漏

时间:2010-04-21 12:59:10

标签: wpf mvvm memory-leaks timer

为相当冗长和冗长的帖子道歉,但这个问题让我困扰了几个星期,所以我尽可能多地发布信息,以便迅速解决这个问题。

我们有一个WPF UserControl由第三方应用加载。第三方应用程序是一个演示应用程序,它根据从服务器下载的XML文件定义的计划加载和卸载控件。

我们的控件在加载到应用程序时向Web服务发出Web请求,并使用响应中的数据显示一些信息。我们使用MVVM架构进行控制。控件的入口点是一个实现主应用程序公开的接口的方法,这是控件配置的设置位置。这也是我将控件的DataContext设置为MainViewModel

的地方

MainViewModel有两个其他视图模型作为属性,主UserControl有两个子控件。根据从Web服务接收的数据,主UserControl决定显示哪个子控件,例如如果存在HTTP错误或收到的数据无效,则显示子控件A,否则显示子控件B.正如您所料,这两个子控件绑定两个单独的视图模型,每个视图模型都是{{ 1}}。

现在,子控件B(在数据有效时显示)具有MainViewModel属性/字段。 RefreshService是一个对象,负责以多种方式更新模型,包含4 RefreshService个;

  • a System.Timers.Timer
  • a _modelRefreshTimer
  • a _viewRefreshTimer
  • a _pageSwitchTimer(只有在检索数据出现问题时才会启用)。

此时我应该提到有两种类型的数据;第一个每分钟都在变化,第二个变化每隔几个小时。控件的配置决定了我们正在使用/显示的类型。

  1. 如果数据属于第一类,那么我们会使用_retryFeedRetrievalOnErrorTimer的事件非常频繁地(每30秒)更新一次模型。

  2. 如果数据是第二种类型,那么我们会在更长的时间间隔后更新模型。但是,视图仍需要每30秒刷新一次,因为需要从视图中删除过时数据(因此_modelRefreshTimer)。

  3. 控件还对数据进行分页,以便我们可以看到比显示区域更多的数据。这可以通过将数据分解为列表并将视图模型的_viewRefreshTimer(列表)属性切换到右侧列表来实现。这是通过处理CurrentPage的Elapsed事件来完成的。

    现在问题

    我的问题是,当从可视树中删除时,控件不会丢弃它的计时器。当我们在部署此控件后很快就开始在Web服务器端获得异常大量的请求并发现每秒至少发出一次请求时,首先注意到了这一点!我们发现定时器在控件从视图中删除后仍然存在并且没有停止数小时,并且那些定时器越多,在Web服务器上堆积的请求就越多。

    我的第一个解决方案是为_pageSwitchTimer实施IDisposable并在控件的RefreshService事件被触发时进行一些清理。在UnLoaded的Dispose方法中,我为所有计时器设置了RefreshServiceEnabled,然后在所有计时器上使用了false方法。然后我也调用Stop()并将它们设置为Dispose()。这些都不起作用。

    经过一些阅读后,我发现事件处理程序可能会引用计时器并阻止它们被处理和收集。经过一些阅读和研究后,我发现最好的解决方法是使用Weak Event Pattern。使用this博客和this博客,我设法解决了弱事件模式中的缺点。

    但是,这一切都没有解决问题。计时器仍然没有被禁用或停止(更不用说处理),并且Web请求正在继续建立。 Mem Profiler告诉我“这种类型有N个实例,它们直接由委托生根。这可以表明代理没有被正确删除”(其中N是实例数)。据我所知,所有定时器Elapsed事件的听众都在清理过程中被删除,所以我不明白为什么定时器继续运行。

    感谢阅读。热切期待你的建议/意见/解决方案(如果你到目前为止:-p)

3 个答案:

答案 0 :(得分:3)

您是否尝试过使用DispatchTimer而不是System.Timers.Timer?

如果在WPF应用程序中使用了Timer,则值得注意的是Timer在与用户界面(UI)线程不同的线程上运行。为了访问用户界面(UI)线程上的对象,必须使用Invoke或BeginInvoke将操作发布到用户界面(UI)线程的Dispatcher上。使用与Timer相反的DispatcherTimer的原因是DispatcherTimer在与Dispatcher相同的线程上运行,并且可以设置DispatcherPriority。 (来自this blog

答案 1 :(得分:1)

听起来像定时器信号排队的速度快于处理。例如,如果在每秒计时器过去时每个过去事件需要2秒钟来处理,则会发生这种情况。这将导致在.NET线程池上安排“提高Elapse事件现在”信号的积压。

即使在您停止计时器后,这样的积压也会继续导致事件发生。来自System.Timers.Timer文档:

  

即使SynchronizingObject为true,   经过事件后可能会发生   已调用Dispose或Stop方法   或者在Enabled()属性之后   被设置为假,因为信号   总是提高Elapsed事件   排队等待在线程池上执行   线。解决这场比赛的一种方法   条件是设置一个告诉的标志   Elapsed的事件处理程序   事件忽略后续事件。

为避免在线程池中积累未处理的计时器信号积压,您可以将AutoReset设置为false。这样,定时器在每次经过时都会自行禁用。然后,您可以在处理elapse事件后将Enabled设置回true。这样,在处理完最后一个事件之前,不会安排额外的过时事件。

答案 2 :(得分:0)

调用timer.Elapsed - = new ElapsedEventHandler();当您的控件被处理掉以手动分离处理程序时。