A functional programming-inspired event scheduling library for F# with priority, delayed, and periodic event schedulers. (current version 1.0.1; already stable)
If you're using F# event models for the first time, or want to refresh your knowledge, please see the Beginner's Guide section.
- .NET 8.0 or higher
- F# projects or F# scripts
PriorityEventScheduler: Execute events based on priority (higher number = higher priority)DelayedEventScheduler: Schedule events to execute after specified delaysPeriodicEventScheduler: Execute events at regular intervals with unique identifier managementCompositeEventScheduler: Unified scheduler combining all three types for complex workflows (NEW in version 1.0.1)- Thread-safe: All operations are thread-safe using proper locking
- Functional API design: Provides functional programming patterns while using mutable state internally for performance
- Honest documentation: Clear about implementation trade-offs between functional purity and practical performance
Install the package using NuGet:
Install-Package FSharpEventAddonsOr via .NET CLI:
dotnet add package FSharpEventAddonsIf you're using F# scripts, you can reference the library directly:
#r "nuget: FSharpEventAddons"Import this library to your F# project:
open FSharpEventAddonslet scheduler = PriorityEventScheduler()
scheduler.Schedule((fun () -> printfn "High priority event"), Some 10)
scheduler.Schedule((fun () -> printfn "Low priority event"), Some 1)
scheduler.Schedule((fun () -> printfn "Default priority event"), None)
printfn "Priority scheduler event count: %d" (scheduler.EventCount) // 3 events
scheduler.Execute() // Executes in priority orderlet scheduler = DelayedEventScheduler()
scheduler.ScheduleDelayed((fun () -> printfn "Delayed event"), 1.0) // 1 second delay
// Call ExecuteExpired() periodically to check for expired eventslet scheduler = PeriodicEventScheduler()
let eventId = scheduler.SchedulePeriodic((fun () -> printfn "Periodic event"), 0.5) // Every 0.5 seconds (approx. 500 ms)
// Call ExecuteDue() periodically to execute due eventslet compositeScheduler = CompositeEventScheduler()
// Schedule different types of events
compositeScheduler.SchedulePriority((fun () -> printfn "High priority event"), 10)
compositeScheduler.ScheduleDelayed((fun () -> printfn "Delayed event"), 2.5) // After 2.5 seconds
let periodicId = compositeScheduler.SchedulePeriodic((fun () -> printfn "Periodic event"), 1.0) // Every second
// Execute all events in unified workflow
printfn "Total events: %d" (compositeScheduler.TotalEventCount)
let (priorityCount, delayedCount, periodicCount) = compositeScheduler.GetEventCounts()
printfn "Priority: %d, Delayed: %d, Periodic: %d" priorityCount delayedCount periodicCount
// Execute all pending events
compositeScheduler.ExecuteAll()Schedule(action: unit -> unit, priority: int option) : unit- Schedule an event with priority;Nonefor default priority of 0Execute() : unit- Execute all pending events in priority orderEventCount: int- Get current count of pending eventsClear() : unit- Clear all pending events
ScheduleDelayed(action: unit -> unit, delaySec: float) : unit- Schedule event with delay in seconds (must be greater than 0)ExecuteExpired() : unit- Execute events whose delay has expiredEventCount: int- Get current count of delayed eventsSecondsUntilNextEvent: float option- Time until next scheduled event in secondsClear() : unit- Clear all delayed events
SchedulePeriodic(action: unit -> unit, intervalSec: float) : Guid- Schedule periodic event with seconds interval (must be greater than 0), and returns a unique identifier for the eventExecuteDue() : unit- Execute due events whose interval has elapsedEventCount: int- Get current count of periodic eventsRemovePeriodic(eventId: Guid) : unit- Remove specific periodic eventClear() : unit- Clear all periodic events
SchedulePriority(action: unit -> unit, priority: int option) : unit- Schedule a priority event with optional priority parameterScheduleDelayed(action: unit -> unit, delaySec: float) : unit- Schedule a delayed event with seconds delaySchedulePeriodic(action: unit -> unit, intervalSec: float) : Guid- Schedule a periodic event with seconds interval, returns unique identifierExecuteAll() : unit- Execute all pending events across all schedulers (priority → delayed → periodic)ExecutePriority() : unit- Execute only priority eventsExecuteDelayed() : unit- Execute only expired delayed eventsExecutePeriodic() : unit- Execute only due periodic eventsTotalEventCount: int- Get total count of all pending eventsGetEventCounts() : (int * int * int)- Get individual counts for priority, delayed, and periodic eventsSecondsUntilNextDelayedEvent: float option- Time until next delayed event in secondsRemovePeriodic(eventId: Guid) : unit- Remove specific periodic eventClearAll() : unit- Clear all events from all schedulersClearPriority() : unit- Clear only priority eventsClearDelayed() : unit- Clear only delayed eventsClearPeriodic() : unit- Clear only periodic events
F# provides several built-in event handling mechanisms, but they differ from traditional object-oriented event systems:
let event = Event<int>()
let observable = event.Publish
observable.Add(fun x -> printfn "Event received: %d" x)
event.Trigger(42)let numbers = [1..10]
numbers
|> List.toSeq
|> Observable.ofSeq
|> Observable.filter (fun x -> x % 2 = 0)
|> Observable.add (printfn "Even number: %d")type Message =
| Schedule of (unit -> unit) * int
| Execute
let scheduler = MailboxProcessor.Start(fun inbox ->
let rec loop events = async {
let! msg = inbox.Receive()
match msg with
| Schedule(action, priority) ->
return! loop ((action, priority) :: events)
| Execute ->
events
|> List.sortBy snd
|> List.iter (fst >> Async.RunSynchronously)
return! loop []
}
loop [])While F# has built-in event handling, FSharpEventAddons provides:
- Priority-based execution - Built-in priority queues
- Time-based scheduling - Precise delayed and periodic execution
- Functional composition - Designed with functional programming principles
- Thread safety - Safe for concurrent usage
- Simple API - Easy to understand and use
| Feature | F# Events | MailboxProcessor | FSharpEventAddons |
|---|---|---|---|
| Priority scheduling | ❌ | ✅ | |
| Delayed execution | ❌ | ✅ | |
| Periodic execution | ❌ | ✅ | |
| Thread safety | ✅ | ✅ | ✅ |
| Functional design | ✅ | ✅ | ✅ |
let priorityScheduler = PriorityEventScheduler()
let delayedScheduler = DelayedEventScheduler()
// Schedule a delayed event that adds a priority event
delayedScheduler.ScheduleDelayed(
(fun () ->
priorityScheduler.Schedule(
(fun () -> printfn "Delayed priority event"),
Some 1)),
2.0)let safeScheduler = PriorityEventScheduler()
safeScheduler.Schedule(
(fun () ->
try
// Your event logic here
printfn "Event executed successfully"
with
| ex -> printfn "Event failed: %s" ex.Message),
None)Honest Assessment of Functional Purity vs. Practical Performance
This library makes a deliberate trade-off: it provides a functional API while using mutable state internally for performance reasons. Here's why:
- Performance: Pure functional implementations with immutable state would require copying the entire event list on every operation
- Thread Safety: The mutable state is properly locked, ensuring thread safety without sacrificing performance
- Practicality: For event scheduling systems that handle many events, immutable state would be prohibitively expensive
Despite the mutable implementation, the library embraces functional programming principles:
- Immutable Data Structures: Event items are F# records (immutable by default)
- Pure Functions: Operations like sorting and filtering use pure functional patterns
- Functional Composition: API designed for functional composition and pipelining
Use FSharpEventAddons when:
- You need high-performance event scheduling
- You're dealing with many events (100+)
- Thread safety is a requirement
- You want a simple, practical API
Consider pure functional alternatives when:
- You're dealing with very few events (< 10)
- Functional purity is more important than performance
- You're building a learning/educational project
- PriorityEventScheduler: O(n log n) for execution due to sorting
- DelayedEventScheduler: O(n) for execution, efficient for small numbers of events
- PeriodicEventScheduler: O(n) for execution, efficient interval checking
For high-performance scenarios, consider batching events or using specialized data structures.
git clone https://github.com/Pac-Dessert1436/FSharpEventAddons.git
cd FSharpEventAddons
dotnet build
dotnet test # If tests are added laterContributions are welcome! Please feel free to submit pull requests or open issues for bugs and feature requests.
This project is licensed under the BSD 3-Clause License. See the LICENSE file for details.
- 1.0.1: Added
CompositeEventSchedulerwith existing schedulers combined - 1.0.0: Stable release with priority, delayed, and periodic event schedulers