Appearance
ComputedOptions: Fine-Tuning Compute Methods
This document covers the advanced configuration options available for compute methods via the [ComputeMethod] attribute and ComputedOptions.
Overview
Every compute method uses ComputedOptions to control:
- How long computed values stay in memory
- When and how they auto-invalidate
- How invalidation timing works
- How multiple concurrent updates are consolidated
You configure these options via the [ComputeMethod] attribute:
cs
[ComputeMethod(MinCacheDuration = 10, AutoInvalidationDelay = 60)]
Task<UserProfile> GetProfile(string userId);How Values Are Interpreted
All [ComputeMethod] properties are double values representing seconds. They use special values to express "use default" and "infinite/disabled":
double value | Meaning |
|---|---|
double.NaN | "Use default" — inherits from ComputedOptions.Default |
double.PositiveInfinity | "Infinite" / disabled — translates to TimeSpan.MaxValue |
>= 0 | Actual duration in seconds |
< 0 | Invalid — throws ArgumentOutOfRangeException |
All attribute properties default to double.NaN, so omitting an option means "use the global default":
cs
// These are equivalent:
[ComputeMethod(MinCacheDuration = double.NaN)]
Task<Data> GetData1();
[ComputeMethod] // MinCacheDuration not specified = use default
Task<Data> GetData2();
// Explicitly disable auto-invalidation:
[ComputeMethod(AutoInvalidationDelay = double.PositiveInfinity)]
Task<Data> GetData3();Option Reference
MinCacheDuration
Type: double (seconds) Default: 0 (no minimum)
Minimum time a Computed<T> instance stays in RAM via a strong reference.
cs
[ComputeMethod(MinCacheDuration = 60)] // Keep in memory for at least 60 seconds
Task<User> Get(string id);How it works:
- When a computed value is created, Fusion holds a strong reference to it for this duration
- Without this, computed values may be garbage-collected as soon as no code references them
- Invalidation trims this time — there's no reason to cache an outdated value
When to use:
- For frequently accessed data that's expensive to compute
- For data that's shared across many UI components
- Authentication/session data that shouldn't be recomputed on every access
TransientErrorInvalidationDelay
Type: double (seconds) Default: 1
Auto-invalidation delay for computed values that store a transient error (e.g., network failures).
cs
[ComputeMethod(TransientErrorInvalidationDelay = 5)] // Retry after 5 seconds
Task<Data> FetchFromExternalApi();How it works:
- If a compute method throws a transient exception, the error is cached
- After this delay, the computed value auto-invalidates, triggering a retry
- Helps recover from temporary failures without manual intervention
When to use:
- External API calls that may fail temporarily
- Database operations that might hit connection limits
- Any operation where retry after a delay makes sense
NonTransientErrorInvalidationDelay
Type: double (seconds) Default: 30
Auto-invalidation delay for computed values that store a non-transient error — one your TransiencyResolver did not classify as transient.
cs
[ComputeMethod(NonTransientErrorInvalidationDelay = 300)] // Clear a stuck error after 5 minutes
Task<Report> BuildReport(string id);How it works:
- A non-transient error is otherwise cached until something invalidates it.
- The effective delay for
NonTransient/Unknownerrors ismin(AutoInvalidationDelay, NonTransientErrorInvalidationDelay)— so a service that lowersAutoInvalidationDelayto cap the value's lifetime also caps its error lifetime, while this 30-second default keeps a non-deterministic error that was classified non-transient (e.g. a race-inducedNullReferenceException) from staying cached indefinitely. Terminalerrors useAutoInvalidationDelayalone (ignoring this delay), since re-executing them can never help — retrying aTerminalerror is pointless.- Set it to
double.PositiveInfinityto keep non-transient errors cached until an explicit invalidation (or untilAutoInvalidationDelay, if set). - MutableState is an exception:
ComputedOptions.MutableStateDefaultoverrides both this delay andTransientErrorInvalidationDelaytoTimeSpan.MaxValue, so a value (or error) you set on aMutableStatestays put until you explicitly change it — the error horizon doesn't apply.
When to use:
- Rarely needs changing — the 30-second default is a safety net for errors that are classified non-transient but are actually non-deterministic.
- Raise it for genuinely permanent errors you want cached longer; lower it to retry sooner.
AutoInvalidationDelay
Type: double (seconds) Default: TimeSpan.MaxValue (no auto-invalidation)
Time after which a computed value automatically invalidates itself.
cs
[ComputeMethod(AutoInvalidationDelay = 30)] // Auto-refresh every 30 seconds
Task<DateTime> GetServerTime();How it works:
- The computed value schedules its own invalidation after this delay
- Useful for data that naturally becomes stale over time
- Works even if no external invalidation occurs
When to use:
- Clock/time-based data
- Polling external systems where push notifications aren't available
- Data with known expiration (e.g., cached tokens)
- Rate-limited refresh of volatile data
InvalidationDelay
Type: double (seconds) Default: 0 (immediate)
Delay before invalidation actually takes effect.
cs
[ComputeMethod(InvalidationDelay = 0.5)] // Debounce invalidations by 500ms
Task<Summary> GetSummary();How it works:
- When
Invalidate()is called, the actual invalidation is postponed - Multiple rapid invalidations during this period are coalesced into one
- Reduces recomputation storms during batch updates
When to use:
- Aggregate computations that depend on many rapidly-changing values
- Debouncing UI updates during batch operations
- Reducing server load during bulk data imports
ConsolidationDelay
Type: double (seconds) Default: -1 (no consolidation)
Eliminates "false" invalidations by only invalidating when the computed value actually changes.
cs
[ComputeMethod(ConsolidationDelay = 0)] // Invalidate only when value changes
Task<int> GetUnreadCount(string placeId);
[ComputeMethod(ConsolidationDelay = 0.5)] // Wait 500ms before checking for value changes
Task<Summary> GetSummary();How it works: When ConsolidationDelay is zero or positive, Fusion backs the method with two computed instances:
- Consolidation target — the instance everyone sees from the outside
- Consolidation source — the internal instance used to detect actual value changes
The consolidation target doesn't have any dependencies itself, but listens to consolidation source invalidations. When an invalidation occurs on the consolidation source:
- The target waits for the consolidation delay (can be zero)
- Then recomputes the consolidation source
- If the new value differs from the target's current value, the target invalidates itself
- If the value is the same, the invalidation is "swallowed" and the target continues listening to the new source
Why this matters: Without this option, every ground truth invalidation in Fusion spreads through the entire dependency graph, hitting every dependent computed. It doesn't matter if some invalidated values recompute to exactly the same result — if your dependency is invalidated, you get invalidated too.
Example — Unread counters: A single unread counter may depend on hundreds of API calls (e.g., a place counter depends on every chat counter, which depend on read/write positions). Without consolidation, a single post in any chat would invalidate the chat counter (even though it likely produces the same value), then the place counter, then the total — forcing every UI element showing these counters to refresh.
With ConsolidationDelay, the counter only invalidates when its value actually changes.
When to use:
- Counter-like aggregations (unread counts, totals, statistics)
- Any computed that frequently recomputes to the same value
- Reducing invalidation cascades in deep dependency graphs
- Preventing unnecessary UI refreshes
Combining Options
Options can be combined for sophisticated caching strategies:
cs
// Long-lived cache with automatic refresh
[ComputeMethod(
MinCacheDuration = 300, // Keep in memory 5 minutes
AutoInvalidationDelay = 60)] // But refresh every minute
Task<Stats> GetDashboardStats();
// Resilient external call with debouncing
[ComputeMethod(
TransientErrorInvalidationDelay = 10, // Retry errors after 10s
InvalidationDelay = 1)] // Debounce updates by 1s
Task<Price> GetExternalPrice(string symbol);
// Aggregation that should only invalidate when value changes
[ComputeMethod(
MinCacheDuration = 60,
ConsolidationDelay = 0)] // Invalidate only on actual value change
Task<int> GetTotalUnreadCount();Default Values
Fusion provides different defaults for server-side and client-side (remote) compute services:
| Option | Server Default | Client Default |
|---|---|---|
| MinCacheDuration | 0 | 60 seconds |
| TransientErrorInvalidationDelay | 1 second | 1 second |
| NonTransientErrorInvalidationDelay | 30 seconds | 30 seconds |
| AutoInvalidationDelay | ∞ (none) | ∞ (none) |
| InvalidationDelay | 0 | 0 |
| ConsolidationDelay | -1 (none) | -1 (none) |
MutableState uses its own defaults (ComputedOptions.MutableStateDefault): both TransientErrorInvalidationDelay and NonTransientErrorInvalidationDelay are ∞, so its value or error is never auto-invalidated and stays until you change it explicitly.
You can change global defaults by modifying ComputedOptions.Default and ComputedOptions.ClientDefault at startup:
cs
ComputedOptions.Default = ComputedOptions.Default with {
MinCacheDuration = TimeSpan.FromSeconds(30),
};Remote Compute Methods
For distributed scenarios, use [RemoteComputeMethod] which extends [ComputeMethod] with caching options:
cs
[RemoteComputeMethod(CacheMode = RemoteComputedCacheMode.Cache)]
Task<Product> Get(string id);RemoteComputedCacheMode values:
Default— inherit fromComputedOptions.ClientDefaultCache— enable client-side caching of remote resultsNoCache— disable caching, always fetch from server
Tips
- Assume defaults are fine — make changes once you know what's going on; you can also change
ComputedOptions.Defaultglobally - Consider memory —
MinCacheDurationtrades memory for CPU; balance accordingly - Mind the invalidation chain — delayed invalidation affects all dependent computed values
- Test consolidation carefully — too long a delay can show stale data; too short defeats the purpose
