System.Timers.Timer leaking due to "direct delegate roots"
Apologies for the rather verbose and long article, but this issue has been puzzled by me for several weeks, so I am posting as much information as possible to quickly resolve this issue.
We have WPF UserControl
which is loaded by a third party application. A third party application is a presentation application that loads and unloads controls on a schedule defined by an XML file downloaded from the server.
Our control, when loaded into the application, makes a web request to the web service and uses the data from the response to display some information. We use MVVM architecture for control. The entry point of a control is a method that implements the interface exposed by the host application, and this is where the control configuration is set up. This is also I set DataContext
our control to ours MainViewModel
.
MainViewModel
has two other view models as properties, and the main one UserControl
has two child controls. Based on the data received from the web service, the main one UserControl
decides which child control to display, eg. if there is an HTTP error or the data received is invalid then render child control A, otherwise render child control B. As you would expect, these two child controls link two separate view models, each of which is a property MainViewModel
.
Now child control B (which is displayed when the data is correct) has a RefreshService
property / field property. RefreshService
is an object that is responsible for updating the model in several ways and contains 4 System.Timers.Timer
s;
- a
_modelRefreshTimer
- a
_viewRefreshTimer
- a
_pageSwitchTimer
- a
_retryFeedRetrievalOnErrorTimer
(this is allowed only when something fails with the data being received).
At this point, I must mention that there are two types of data; the first one changes every minute, the second one changes every few hours. The controls configuration determines what type we use / show.
-
If the data is of the first type, we update the model frequently (every 30 seconds) using events
_modelRefreshTimer
. -
If the data is of the second type, we update the model after a longer interval. However, the view still needs to be refreshed every 30 seconds because the stale data needs to be removed from the view (hence
_viewRefreshTimer
).
The control also breaks pages into data so that we can see more than we can fit in the display area. This works by breaking the data into Lists and switching the property CurrentPage
(which is a list) of the view model to the right list. This is done by handling the _pageSwitchTimer
Elapsed event .
Now the problem
My problem is that the control removed from the visual tree is not recycling the timers. This was first noticed when we started getting an unusually high number of requests to the web server end very soon after deploying this control and found that requests were being made at least once a second! We found that timers live and don't stop hours after the control was removed from view, and that the more timers, the more requests were uploaded to the web server.
My first solution was to implement IDisposable
for RefreshService
and do some cleanup when the control event is fired UnLoaded
. In the RefreshService
Dispose method, I set Enabled
in false
for all the timers and then used the method Stop()
for all of them. Then I called Dispose()
and installed them in null
. None of this worked.
After some reading, I discovered that event handlers can contain references to timers and prevent them from being removed and collected. After some more reading and research, I found that the best way was to use the Weak Event Pattern . Using this blog and this blog I was able to fix the flaws in the weak events pattern.
However, none of these problems solve the problem. Timers are still not disabled or stopped (let alone recycled) and web requests continue to ramp up. Mem Profiler tells me that "this type has N instances that are directly associated with the delegate. This could indicate that the delegate was not removed correctly" (where N is the number of instances). As far as I can tell, all listeners for the Elapsed event for the timers are removed during cleanup, so I can't figure out why the timers keep running.
Thanks for reading. Looking forward to your suggestions / comments / solutions (if you got this: -p)
a source to share
Have you tried using DispatchTimer instead of System.Timers.Timer?
If the timer is used in a WPF application, it's worth noting that the timer is started on a different thread and then on the UI thread. To access objects on the user interface (UI) thread, you must send an operation to the UI thread dispatcher thread using Invoke or BeginInvoke. The reasons for using DispatcherTimer versus Timer are because the DispatcherTimer runs on the same thread as the Dispatcher and DispatcherPriority. (from this blog )
a source to share
It seems to me that timer signals are queued faster than processed . For example, this will happen if each elapse event takes 2 seconds to process, while the timer expires every second. This will cause the .NET thread pool to schedule the "raise Elapse event now" tracking.
This lag will continue to trigger evacuation events even after the timer has stopped. From the System.Timers.Timer doc :
Even if SynchronizingObject is true, Elapsed events can occur after Dispose or Stop is called or after the Enabled () property has been set to false, because the signal to raise an Elapsed event is always queued for execution on the thread pool thread. One way to solve this race condition is to set a flag that tells the event handler for an expired event to ignore subsequent events.
To avoid lagging the raw timer signals growing in the thread pool, you can set AutoReset to false. Thus, the timer is disabled at each expiration. Then you can set Enabled to true after handling the elapse event. Thus, until the latter is processed, no additional events will be scheduled.
a source to share