- Table of Contents
- .NET Book Recommendations
- Getting Started with .NET
- The Microsoft .NET Framework
- The Common Language Runtime (CLR), the Common Type System (CTS), and the Common Language Specification (CLS)
- .NET Framework Class Library
- Visual Studio .NET
- .NET Enterprise Servers and .NET My Services
- .NET Compliant Languages
- C#
- Visual Basic .NET (VB .NET)
- ASP.NET
- XML Web Services
- ADO.NET
- XML.NET
- Windows Forms
- Why .NET?
- Displaying Errors with the Error Provider
- COM Interoperability
- Comparing Java and .NET
- Calling Unmanaged Code
- .NET Application Security
- Code Access Security
- .NET Standards Support
- Numeric Types in the .NET Framework
- Working with Strings
- Formatting Strings
- Trimming Character Strings
- Comparing Strings in .NET 2.0
- Arrays and Collections
- Arrays as Class Members
- Sorting a Multi-Dimensional Array
- Sorting a Multi-Dimensional Array with LINQ
- File I/O (System.IO)
- Working with File Names
- Using the File System
- Working with Files and Directories
- Monitoring the File System
- Working with Streams
- Working with Text Encodings
- Working with Date and Time
- Extending the DateTime Class
- Using DateTimeOffset
- Fun with Dates
- Exceptions
- Delegates
- Events
- Asynchronous Programming
- Asynchronous File I/O
- Timers
- Random Numbers
- Cryptographically Secure Random Numbers
- Serialization
- MultiThreading (System.Threading)
- Multi-Threading Overview
- The Managed Thread Pool
- Managed Threading
- Thread Synchronization
- Synchronizing Data Access
- Trace Debugging
- Tracing in .NET 2.0
- ASP.NET Trace
- Validating User Input in ASP.NET Web Pages
- Event Logging
- Monitoring Application Performance
- Accessing the Registry
- Accessing Environment Information
- Environment Variables in .NET 2.0
- Managing Windows Forms Applications
- Working with Email
- Working with Graphics
- Animating a Background
- Working with Images
- Drawing Cycloid Curves
- Simulating the Spirograph
- Building International Web Applications
- .NET Compact Framework
- Mobile Web Development with ASP.NET
- Speech Technologies
- Microsoft MapPoint Web Service
- Working with Typed DataSets
- Using Relationships in DataSets
- DataColumn Expressions
- Playing Simple Sounds
- Playing Sounds with .NET 2.0
- Returning an Image in a Web Page
- RSS
- Best Practices Project Structure
- Best Practices Application Blocks
- The Data Access Application Block
- The Exception Management Application Block
- Best Practices — Performance
- Best Practices — Performance and Scalability
- Best Practices - Testing
- Reading the Tea Leaves, 2005
- Predictions: A Look Back at 2005, and a Look Ahead to 2006
- .NET Downloads
- Application Deployment Overview
- Application Deployment — Versioning
- Application Deployment — Version Policy
- Application Deployment — Packaging and Distribution
- .NET Remoting Overview
- A Remoting Demonstration
- Remoting Configuration
- Remoting: Lifetimes and Leases
- Remoting: Other Issues
- Attributes
- Writing Custom Attributes
- Accessing Attributes in Code
- Reflection
- Class Design: Inheritance, Interface, or Composition?
- The TriTryst Game
- Console Applications in .NET 2.0
- New File I/O Methods in .NET 2.0
- Building Projects with MSBuild
- Unmanaged Callbacks in .NET 2.0
- Timer Troubles
- Non-Rectangular Windows Forms
- Windows Forms Transparency
- 10 Things I Hate About Visual Basic
- 10 Things I Hate About C#
- Background Processing with Idle Time
- Scaling Windows Forms
- Reading and Writing Binary Data
- New Memory Management Functions in .NET 2.0
- Compatibility Between .NET 1.1 and .NET 2.0
- Managed Debugging Assistants in .NET 2.0
- XDir: A Program for Viewing Directory Sizes
- The Microsoft.VisualBasic Namespace
- Operator Overloading
- Working with GPS Data
- Hidden Visual Studio Tools
- .NET 3.0
- The .NET 2.0 Stopwatch Class
- Nullable Types
- Drawing Rotated Text
- Unsafe Code
- Other .NET Languages
- Compiler Directives
- Safe Handles
- Predictions, 2007 Edition
- New Features in C# 3.0
- Generics
- Network Client Programming
- On the Misuse of Exceptions
- Maximum Object Size in .NET
- More on Maximum Object Sizes
- Keyed Collection Memory Limitations
- Matching String Endings
- Allocating Small Data Structures
- Grumbling About Limitations
- Some Thoughts on the Nature of What We Do
- Working with Predicates in Collections
- Working with DataReaders
- Outputting XML with XmlWriter
- Writing XML Data
- Working with Compression
- Another Look at Compressed Streams
- Compressing a Very Large File
- Canonical URIs
- Constructing URIs
- Using OneWayAttribute for Remote Calls
- Selecting a Garbage Collector
- Linked List
- Linked List Application - The MRU List
- Auto-implemented Properties in C#
- The HashSet Collection
- Looking Ahead: 2018
- An Experiment in Optimization
- A Larger Integer
- Extension Methods
- Language Integrated Query (LINQ)
- Variable Length Parameter Lists
- The ReaderWriterLockSlim Synchronization Primitive
- Sorting a Text File
- Sorting a Large Text File
- Using ListView with Large Data Sets
- LINQ One-Liners
- Regular Expression Optimization
- Random File I/O
- Computing the Size of a Structure
- More on Computing Structure Sizes
- UnmanagedMemoryStream
- Dynamically Loading Code
- Building a String Table
- Delegates Versus Function Pointers
- Visual Studio Editor Features
- A Simple Profile Timer
- New Features in C# 4.0
- IEnumerator or IList?
- New Features in .NET 4.0
- Set Operations with IEnumerable and HashSet
- Using File Locks
- Extending Object Functionality
- Clearing a HashSet
- When Hash Codes Matter
- Parsing Command Line Options
- Creating a Single-Instance Program
- Asynchronous Windows Forms Events
- The BackgroundWorker Component
- Fixing a Dumb Mistake
- Thinking About Multi-Threaded Programs
- JavaScript Object Notation
- Better JSON Processing with JSON.Net
- Useful .NET-related Sites
- Markov Models
- Building an Order 0 Markov Model
- Higher Order Markov Models
- Webmaster's Guide to robots.txt
- An Overview of the Parallel Extensions to .NET
- Parallel Extensions Synchronization Objects
- Thread Safe Collections
- A Bug and a Conundrum
- Another Bug and an Answer
- Task Parallel Library
- Good and Bad Ideas in C#
- Parallel LINQ
- Copying Large Files
- Replacing File.Copy
- Learning from Our Mistakes
- Symbolic Links
- There Is No Easy Fix
- Tracking Hurricanes
- Examining Hurricane Data
- Searching for Multiple Strings
- Simple JSON Processing
- Aho-Corasick String Searching
- Writing a Web Crawler
- Web Crawler Politeness
- Source Control Management
- Subversion
- Communicating with Datagrams
- Fun with Actions and Funcs
- The Future of Media
- The Importance of Metadata
- Of Comparison and IComparer
- IComparer, Comparer, IComparable, Oh My!
- Comparing Generic Types
- A Simple HTTP Server
- Quantizing DateTime Fields
- More Fun with the Garbage Collector
- Refactor, Don't Rewrite
- A Generic BinaryHeap Class
- A Generic File Sorter
- Birthdays, Random Numbers, and Hash Keys
- Random Selection from Large Groups
- Command Line Tools for Windows
- Reading and Writing, Bit by Bit
- Selecting the Top N Items from a Group
- Determining Website Content Encoding
- Benefits and Drawbacks of Syndication
- Pubsubhubbub
- Memory Use Misconceptions
- Risk, Lost Opportunity, and Other Hidden Upgrade Costs
- Culture Shock: from .NET to JavaScript
- Using .NET for a Startup
- Tracking Wikipedia Changes with IRC
- Browser Applications and the Same Origin Policy
- Handling the Unexpected
- Dealing with Growth
- Deleting the Oldest File
- Where Do I Put Stuff?
- .NET Timer Resolution
- Exploring Options for Better Timers
- Using the Windows Timer Queue API
- Locks Aren't Slow
- Alternatives to Locks
- Lock Free Concurrent Collections
- The BlockingCollection Class
- Customizing BlockingCollection
- What Time Is It? Daylight Saving Time and Computers
- Using enums to Save Memory
- New File Operations in .NET 4.0
- Building a Hierarchy of Rectangles
- A Faster File Copy
- Constants Are Forever
- The Dangers of Floating Point
- Goto is Not Inherently Evil
- The Weakest Link
- Reducing Memory Required for Strings
- Grouping with LINQ
- HttpListener "Gotchas"
- Extension Methods Are Evil
- Finding the Registered Domain in a URL
- Drawing Text
- Obfuscating Sequential Keys
- Properties of Obfuscated Keys
- Finding Changes Between Two Lists
- Using the ConcurrentBag Collection
- Never Sleep!
- Shuffling and Sorting
- Viewing Large Text Files
- Use the Right Tool
- Why GetHashCode Matters
- Optimization Guidelines
- Timer Differences
- The Mutex
- Modifying a Working System
- Building a New Type of Stream
- More Large File Problems
- A Better File.Copy Replacement
- Throwing the Wrong Exception
- Approximate Counters
- Monitoring a Timer
- Combining Consoles and Forms
- Embedding a Text Resource
- Handling Concurrent Downloads
- The Importance of Domain Knowledge
- Stupid Programmer Tricks
- Aho-Corasick Revisited
- Expressiveness is the Soul of Brevity
- Fun with Anonymous Types
- Simplifying a Multi-Threaded Application
- Work Smarter
- The Skip List Data Structure
- A More Memory-Efficient Skip List
- Selection Revisited
- Why Async?
- What the Future Holds
- The "Roslyn" CTP
- Where We've Been
- Informit Reference Library
Exploring Options for Better Timers
Last updated Sep 24, 2010.
As I showed previously, .NET timer resolution is limited to about 15 milliseconds (66 ticks per second). Accuracy of the timers is somewhat limited, too. My testing showed that you can expect a tick to occur within 30 milliseconds of when it was supposed to occur. Typically it's within 15 ms, but there are situations where that isn't true.
I'll grant that it's not often that you want a timer to notify you more often than 66 times per second, but it does happen. And there are many situations when you'll want a timer tick to happen as closely as possible to exactly on time. When you ask for a tick frequency of 40 times per second (once every 25 milliseconds), getting a tick every 40 milliseconds (25 times per second) just doesn't cut it.
Can we get better timer resolution under Windows?
A correction
In my previous article, I stated that the .NET thread timers are based on Windows Timer Queue Timers. That's true, in a sense. At the time I wrote that, I believed thatSystem.Threading.Timer was essentially a managed wrapper around the Timer Queue API. It turns out that the "timer queue" used in .NET is implemented in the runtime and doesn't call the Timer Queue API at all. Thread timers are based on Windows Timer Queues only in the sense that the API and implementation are very similar.
I apologize if my statement in the previous article caused any confusion.
Windows timer objects
Windows has been around in one incarnation or another for over 20 years. In that time it has supported a bewildering number of different timers, all of which appear to still be supported. The backward compatibility is commendable, but trying to make sense of the different timers and when to use each is at best confusing. At worst, choosing the wrong type of timer can lead to poor application performance, high CPU usage, decreased battery life on mobile devices, and potential deadlocks.
Early versions of Windows had a single timer object, which was created by calling theSetTimer API function. This timer was based on the default DOS timer interrupt, and had a maximum resolution of about 54 milliseconds. This is the same timer that's used by theWindows.Forms.Timer component in .NET, although resolution these days is the same as for other .NET timers: about 15 milliseconds. This timer is commonly referred to as the WM_TIMER timer, because it posts aWM_TIMER message to your window procedure.
Windows 3.1 (perhaps earlier) included a multimedia timer that in theory supported a resolution of one millisecond. Actually achieving one millisecond resolution turned out to be somewhat difficult due to hardware limitations, but this timer was a huge improvement over the WM_TIMER timer, primarily because:
Accuracy was much improved. If you asked for a timer frequency of 50 ms, you got 50 ms. With WM_TIMER, accuracy was limited to increments of 54 ms.
Re-entrancy was supported. With WM_TIMER, you do not get another timer tick until the first one is fully processed. So if it took too long to process a tick, you'd miss the next one. In general, it's a bad idea for your timer callback to take longer than the timer period to process, but sometimes it's unavoidable. The multimedia timer will happily call your callback at every tick, regardless of whether the previous callback has returned.
The multimedia timer supports periodic ticks (one tick every 50 milliseconds, for example), or can be operated as a one-shot: fire a tick and then stop. In addition, it can be configured to call a callback function or to signal or pulse an event. All in all, the multimedia timer was a welcome addition to the Windows API, and with today's hardware can easily achieve one millisecond accuracy.
Useful as the multimedia timer was in the past, current documentation for thetimeSetEvent API function says that the multimedia timers are obsolete and recommends using a timer queue timer (see below).
Windows 2000 (perhaps a version of Windows NT before that) introduced the Waitable Timer synchronization object, which combines the functionality of a periodic timer and a waitable synchronization object. As with other Windows synchronization objects, waitable timers can be named and shared among processes, so one process can create and set the timer, and others can wait on it to be signaled. Like the multimedia timers, waitable timers can be configured as periodic timers or one-shots. In addition, the event can be configured as a manual reset or auto reset. The timer is in a real sense similar to combining a .NET timer object with aManualResetEvent orAutoResetEvent.
Waitable timers have one other very cool feature: they can "wake" your system from sleep or hibernation at a given time. You can set the timer for midnight, put your laptop in hibernation, and go to bed knowing that at midnight your computer will wake up, the timer will fire, and any program that's waiting on the event will get the signal and continue processing. My article, Waitable Timers in .NET with C# shows how to use this type of timer in C# programs.
Windows 2000 also introduced Timer Queues and Timer Queue Timers. The idea behind timer queues is that you can have multiple logical timers controlled by a single timer interrupt. The timer interrupt occurs periodically (for example, once every 15 milliseconds), and any of the logical timers whose times have expired since the last interrupt are fired and rescheduled. This design is much more efficient because a logical timer requires relatively few bytes in memory, and almost no processor resources except when its due time expires.
Like the multimedia timer, timer queue timers have a maximum resolution of one millisecond, which is easily achievable with today's hardware. The API exposes functions that let you create and delete timer queues and individual timers within those queues. The timers themselves have a bewildering number of options for specifying how the callback function is called when the timer period expires. Unfortunately, the documentation is rather sparse on the details of when you should use a particular option.
Windows Vista and Windows Server 2008 introduced one more timer type: the threadpool timer. Conceptually, these timers work in much the same way as the timer queue timers, but they're designed to work with the thread pool design in the newer versions of Windows. The API for these timers is much simplified over the timer queue API.
To my knowledge, those are all of the timer types available in Windows today.
Which one to use?
Of all the timer types, which one should we use to replaceSystem.Threading.Timer? We obviously don't want to use the original Windows timer object that requires a message loop. That's the same timer used by `Windows.Forms.Timer`. We also don't want to use a multimedia timer, since the documentation says that it's obsolete.
The waitable timer is certainly a useful object, and if you need synchronization on a timer it's the only way to go. But as a global synchronization object, it's going to be somewhat heavyweight: you probably don't want to create very many of them.
That leaves us with the timer queue timers and the threadpool timers as the only ones that can support a large number of timers efficiently. And, since the threadpool timers aren't supported in Windows XP, they're out too. I know that Microsoft is about to stop supporting Windows XP, but the truth is that lots of people still use it. So we're left with creating a .NET wrapper around the Timer Queue API. That'll be the project for next time.



