@@ -216,7 +216,7 @@ public RefreshingCache(TimeSpan duration, double eagerRefreshRatio = 0.5, TimeSp
216216 {
217217 _durationTicks = ( long ) ( duration . TotalSeconds * Stopwatch . Frequency ) ;
218218 _eagerRefreshTicks = ( long ) ( _durationTicks * Math . Clamp ( eagerRefreshRatio , 0.0 , 1.0 ) ) ;
219- _softTimeout = softTimeout ?? TimeSpan . Zero ;
219+ _softTimeout = softTimeout ?? Timeout . InfiniteTimeSpan ;
220220 if ( remove is { } r ) _timer = new Timer ( Cleanup , ( long ) ( r . TotalSeconds * Stopwatch . Frequency ) , r * 1.25 , r * 0.25 ) ;
221221 }
222222
@@ -243,13 +243,54 @@ public async ValueTask<V> GetOrAdd(K key, Func<K, Task<V>> factory)
243243 if ( age >= _eagerRefreshTicks )
244244 {
245245 var updateTask = _cache . Update ( key , factory , CallFactory ) ;
246- if ( age >= _durationTicks && ( _softTimeout == TimeSpan . Zero || await Task . WhenAny ( updateTask , Task . Delay ( _softTimeout ) ) == updateTask ) )
247- try { result = await updateTask ; } catch { }
246+ if ( age >= _durationTicks )
247+ {
248+ try
249+ {
250+ result = _softTimeout == Timeout . InfiniteTimeSpan ? await updateTask : await updateTask . WithDefault ( result , _softTimeout ) ;
251+ }
252+ catch { }
253+ }
248254 else
249255 _ = updateTask . ContinueWith ( static t => _ = t . Exception , TaskContinuationOptions . OnlyOnFaulted ) ;
250256 }
251257 return result . Value ;
252258 }
253259
254260 public void Dispose ( ) => _timer ? . Dispose ( ) ;
261+ }
262+
263+ public static class WithDefaultAwaiter
264+ {
265+ /// <summary>Awaits a task returning a default value if it errors or times out.</summary>
266+ public static WithDefaultAwaiter < T > WithDefault < T > ( this Task < T > task , T defaultValue , TimeSpan timeout ) => new ( task , defaultValue , timeout ) ;
267+ }
268+
269+ public sealed class WithDefaultAwaiter < T > ( Task < T > task , T defaultValue , TimeSpan timeout ) : ICriticalNotifyCompletion
270+ {
271+ private Action ? _continuation ;
272+ private Timer ? _timer ;
273+ public WithDefaultAwaiter < T > GetAwaiter ( ) => this ;
274+ public bool IsCompleted => task . IsCompleted ;
275+ public T GetResult ( ) => task . IsCompletedSuccessfully ? task . Result : defaultValue ;
276+ public void OnCompleted ( Action continuation ) => UnsafeOnCompleted ( continuation ) ;
277+ public void UnsafeOnCompleted ( Action continuation )
278+ {
279+ _continuation = continuation ;
280+ _timer = new Timer ( static s => ( ( WithDefaultAwaiter < T > ) s ! ) . Complete ( ) , this , timeout , Timeout . InfiniteTimeSpan ) ;
281+ task . ContinueWith ( static ( t , s ) =>
282+ {
283+ ( ( WithDefaultAwaiter < T > ) s ! ) . Complete ( ) ;
284+ if ( t . IsFaulted ) _ = t . Exception ;
285+ } , this , TaskContinuationOptions . ExecuteSynchronously ) ;
286+ }
287+ private void Complete ( )
288+ {
289+ if ( Interlocked . Exchange ( ref _continuation , null ) is { } continuation )
290+ {
291+ try { continuation ( ) ; }
292+ catch { }
293+ _timer ? . Dispose ( ) ;
294+ }
295+ }
255296}
0 commit comments