One way to approach this is to use a SortedList
of pending events (sorted on their "due date") and to use a timer that's firing frequently enough to satisfy the sort of time intervals that you're wanting to work with (e.g. once every 250ms may be enough here).
Each time the (single) timer fires, it keeps examining the front of the list, and if the item at the front has a due date in the past, then we dequeue the item and arrange for it to run (whether you run the events on the timer thread itself or e.g. start new Task
s to execute them depends to an extent on whether events need to happen in parallel and how long event processing takes).
Normally, each event object will have some kind of Action
embedded in its structure so that this dispatching code can be quite straightforward. Recurring events may enqueue their "next" execution as part of their own actions, or you may choose to make that another form of action taken by the timer thread. (This second model tends to work better if you're doing parallel executions, since it's only the timer thread that interacts with the event queue and so no locking is required).
Finally, how do you run things more frequently? Ditch the timer entirely and just dequeue items from the queue continuously, ignoring their due dates.