Our virtual home

More to know about .NET Timers

As you may know, .NET 1.1 supports three different Timer classes:

  1. Windows timers with the System.Windows.Forms.Timer class
  2. Periodical delegate calling with System.Threading.Timer class
  3. Exact timing with the System.Timers.Timer class

The timings are more or less accurate (see CodeProject: Multithreading in .NET), but that is not the point I want to highlight today. Two sentences from the mentioned codeproject article are important for this post:

"... Events raised from the windows forms timer go through the message pump (together with all mouse events and UI update messages)..."

and

"...the System.Timers.Timer class. It represents server-based timer ticks for maximum accuracy. Ticks are generated outside of our process..."

To report state and newly retrieved items from requested feeds we used a concept to serialize the asynchronous received results from background threads with the help of a timer. This was introduced in the NightCrawler Alpha Dare posted last week for external tests. Some users reported strange failures, memory hog up and bad UI behavior with this Alpha so I would suggest here to not use it anymore for testing if your subscribed feeds count is higher than 20 or 30 feeds.

The idea was not as bad as it seems to be (if you only look at the issues above). The real issue in our case was to use simply the wrong timer class! The UI state refresh includes an update of the unread counters that is reported to the user within the treeview as number postfixes and (more important here) a font refresh (as user decides, default is to mark the feed caption text bold). Have a look to the constructor of the class we used for background thread result processing:

    1 public ThreadResultManager(RssBanditApplication owner, ISynchronizeInvoke syncObject) {

    2       this.owner = owner;

    3       resultInfos = PriorityQueue.Synchronize(new PriorityQueue());

    4

    5       // what we catch on:

    6       this.owner.FeedHandler.UpdateFeedStarted += new NewsHandler.UpdateFeedStartedHandler(this.OnUpdateFeedStarted);

    7       this.owner.FeedHandler.OnUpdatedFeed += new NewsHandler.UpdatedFeedCallback(this.OnUpdatedFeed);

    8       this.owner.FeedHandler.OnUpdateFeedException += new NewsHandler.UpdateFeedExceptionCallback(this.OnUpdateFeedException);

    9

   10       processResult = new System.Timers.Timer(250);

   11       processResult.Elapsed += new System.Timers.ElapsedEventHandler(OnProcessResultElapsed);

   12       processResult.SynchronizingObject = syncObject;

   13       processResult.Start();

   14     }

As you can see in line 10 we used the System.Timers.Timer class. The syncObject parameter is (as you can guess) the main form.

So what happens exactly now if the timer fires? I used the CLR Profiler to get the following exiting results. The event is called in sync. with the SynchronizingObject, means Control::WndProc(m) calls calls into System.Windows.Forms.Control::InvokeMarshaledCallbacks void(), MulticastDelegate::DynamicInvokeImpl()... and then our event method OnProcessResultElapsed(). The allocation graph mentions 101 MB (44.78%) used here! Here is the implementation:

   20 private void OnProcessResultElapsed(object sender, System.Timers.ElapsedEventArgs e) {

   21       // get them out of the resultInfos list and deliver

   22       if (resultInfos.Count > 0) {

   23         ThreadResultInfo t = (ThreadResultInfo)resultInfos.Dequeue();

   24         if (t.Args is NewsHandler.UpdateFeedEventArgs) {

   25           this.owner.OnUpdateFeedStarted(t.sender, (NewsHandler.UpdateFeedEventArgs)t.Args);

   26         } else if (t.Args is NewsHandler.UpdatedFeedEventArgs) {

   27           this.owner.OnUpdatedFeed(t.sender, (NewsHandler.UpdatedFeedEventArgs)t.Args);

   28         } else if (t.Args is NewsHandler.UpdateFeedExceptionEventArgs) {

   29           this.owner.OnUpdateFeedException(t.sender, (NewsHandler.UpdateFeedExceptionEventArgs)t.Args);

   30         }

   31       }

   32     }

As you can see: no real magic things happen. Interesting is the line 22: if there are no queued results, we just return and do nothing. But there was already a cost calling it in sync with the main UI thread, look at the call chain above! And as such it breaks the UI thread every 250 msecs also if there is just nothing to do.

But lets look what will happen if there is a result to process: let's pick the call into owner.OnUpdatedFeed(). The owner forwards to the UI class and no .InvokeRequired call is needed (the only good thing so far). It calls into FeedTreeNodeBase class to update the unread counter and node text of the treeview that in turn calls the TreeNode::UpdateNode() function. It will call into the treeview:WndProc(m), WmNotify(), CustomDraw()..., FontHandleWrapper::.ctor,... Font::ToHFont(), Font:ToLogFont() and...? You are right: it checks the code access security with CodeAccessPermission::Demand()! Right and secure, but slow. More slow even though the CodeAccessSecurityEngine::Check calls into a AppDomain::MarshalObject(Object) followed by a parameter memory serialization into another AppDomain! I scratched my head: what the f...? Then I remembered the second quote (above): Ticks are generated out of process! So this was the obvious reason for the cross-AppDomain call (memory allocation: 75MB, 33%).

So what to do to fix the problem(s)? Simply use the Windows.Forms.Timer! Think about it: it is driven by the main window message pump and runs always in the right context of the main UI thread (no .InvokeRequired calls). Timing isn't an important point here, we just want to process one result each time we are called. Further: no cross-AppDomain security check should happen anymore! With that timer it is just a simple update control(s) with some fresh data!

So take care of the timer class(es) you may use in your projects! Check their implications! Be careful, there are also other issues with it I did not mentioned here, read the docs!
Now I'm off to close related bugs...

» Similar Posts

  1. RSS - Editing a yet published post should NOT change the post date
  2. Remoting from CLR 1.1 to 2.0 and back: issue 1
  3. How to integate and far eastern briefly before Easter
There are no comments.

Comments are closed