/// <summary> /// Creates a default scheduler function for reactions. /// </summary> /// <param name="options">The options to run the scheduler with.</param> /// <param name="action">The action to execute.</param> /// <returns>A Scheduler function.</returns> private static Func <Task> CreateSchedulerFromOptions(AutorunOptions options, Func <Task> action) { return(async() => { await Task.Delay(options.Delay).ConfigureAwait(true); await action().ConfigureAwait(true); }); }
/// <summary> /// Creates an autorun that observes and runs the given predicate until it returns true. /// Once that happens, the given effect is executed and the autorunner is disposed. /// The function returns a disposer to cancel the autorunner prematurely. /// </summary> /// <param name="sharedState">The shared state to use.</param> /// <param name="predicate">The predicate to match.</param> /// <param name="effect">The effect to run.</param> /// <param name="whenOptions">The options for this When.</param> /// <returns>A disposer to cancel the autorunner prematurely.</returns> public static IDisposable When(this ISharedState sharedState, Func <bool> predicate, Action effect, WhenOptions whenOptions = null) { if (sharedState is null) { throw new ArgumentNullException(nameof(sharedState)); } if (predicate is null) { throw new ArgumentNullException(nameof(predicate)); } if (effect is null) { throw new ArgumentNullException(nameof(effect)); } if (whenOptions is null) { whenOptions = new WhenOptions(); } IDisposable result = null; Reaction reaction = null; CancellationTokenSource cancellationTokenSource = null; whenOptions.Name = !string.IsNullOrEmpty(whenOptions.Name) ? whenOptions.Name : $"When@{sharedState.GetUniqueId()}"; if (whenOptions.TimeOut.HasValue) { #pragma warning disable CA2000 // Dispose objects before losing scope cancellationTokenSource = new CancellationTokenSource(); #pragma warning restore CA2000 // Dispose objects before losing scope var taskScheduler = sharedState.GetTaskScheduler(); Task.Factory.StartNew( async() => { var token = cancellationTokenSource.Token; await Task.Delay(whenOptions.TimeOut.Value, token).ConfigureAwait(true); if (!token.IsCancellationRequested) { throw new TimeoutException( string.Format(CultureInfo.CurrentCulture, Resources.TimeoutOccuredInWhen, whenOptions.Name, whenOptions.TimeOut.Value)); } }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, taskScheduler).Unwrap().ContinueWith( t => { try { if (t.Exception != null) { reaction.ReportExceptionInReaction(t.Exception); } } finally { cancellationTokenSource.Dispose(); result.Dispose(); } }, taskScheduler); } var autorunOptions = new AutorunOptions() { Name = $"{whenOptions.Name}-effect", RequiresObservable = whenOptions.RequiresObservable, }; if (whenOptions.ErrorHandler != null) { autorunOptions.ErrorHandler = (r, e) => whenOptions.ErrorHandler(e); } #pragma warning disable CA2000 // Dispose objects before losing scope result = sharedState.Autorun( r => { reaction = r; if (predicate()) { r.Dispose(); cancellationTokenSource?.Cancel(); effect(); } }, autorunOptions); #pragma warning restore CA2000 // Dispose objects before losing scope return(new DelegateDisposable(() => { result.Dispose(); cancellationTokenSource?.Cancel(); })); }
/// <summary> /// Creates a new Autorun reaction. /// </summary> /// <param name="sharedState">The shared state to operate on.</param> /// <param name="expression">The expression that should autorun.</param> /// <param name="autorunOptions">An <see cref="AutorunOptions"/> instance with options.</param> /// <returns>An <see cref="IDisposable"/> instance that can be used to tear down the Autorun reaction.</returns> public static IDisposable Autorun(this ISharedState sharedState, Action <Reaction> expression, AutorunOptions autorunOptions = null) { if (sharedState is null) { throw new ArgumentNullException(nameof(sharedState)); } if (expression is null) { throw new ArgumentNullException(nameof(expression)); } if (autorunOptions is null) { autorunOptions = new AutorunOptions(); } var name = autorunOptions.Name ?? $"Autorun@{sharedState.GetUniqueId()}"; var runSync = autorunOptions.Scheduler == null && autorunOptions.Delay == 0; Reaction reaction = null; #pragma warning disable CA2000 // Dispose objects before losing scope #pragma warning disable IDE0067 // Dispose objects before losing scope if (runSync) { // normal autorun reaction = new Reaction( sharedState, name, () => { ReactionRunner().GetAwaiter().GetResult(); }, autorunOptions.ErrorHandler); } else { var scheduler = CreateSchedulerFromOptions(autorunOptions, ReactionRunner); // debounced autorun var isScheduled = false; reaction = reaction = new Reaction( sharedState, name, () => { if (!isScheduled) { isScheduled = true; scheduler().GetAwaiter().GetResult(); } }, autorunOptions.ErrorHandler); } #pragma warning restore IDE0067 // Dispose objects before losing scope #pragma warning restore CA2000 // Dispose objects before losing scope Task ReactionRunner() { if (reaction.IsDisposed) { return(Task.CompletedTask); } reaction.Track(() => expression(reaction)); return(Task.CompletedTask); } reaction.Schedule(); return(new DisposableDelegate(() => reaction.Dispose())); }
/// <summary> /// Creates a new Autorun reaction. /// </summary> /// <param name="sharedState">The shared state to operate on.</param> /// <param name="expression">The expression that should autorun.</param> /// <param name="autorunOptions">An <see cref="AutorunOptions"/> instance with options.</param> /// <returns>An <see cref="IDisposable"/> instance that can be used to tear down the Autorun reaction.</returns> public static IDisposable Autorun(this ISharedState sharedState, Action <Reaction> expression, AutorunOptions autorunOptions = null) { if (sharedState is null) { throw new ArgumentNullException(nameof(sharedState)); } if (expression is null) { throw new ArgumentNullException(nameof(expression)); } if (autorunOptions is null) { autorunOptions = new AutorunOptions(); } var name = autorunOptions.Name ?? $"Autorun@{sharedState.GetUniqueId()}"; var runSync = autorunOptions.Scheduler == null && autorunOptions.Delay == 0; Reaction reaction = null; #pragma warning disable CA2000 // Dispose objects before losing scope #pragma warning disable IDE0067 // Dispose objects before losing scope if (runSync) { // normal autorun reaction = new Reaction( sharedState, name, ReactionRunner, autorunOptions.ErrorHandler); } else { #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously var scheduler = CreateSchedulerFromOptions(autorunOptions, async() => #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously { ReactionRunner(); }); // debounced autorun var isScheduled = false; reaction = reaction = new Reaction( sharedState, name, () => { if (!isScheduled) { var taskScheduler = sharedState.GetTaskScheduler(); isScheduled = true; Task.Factory.StartNew( scheduler, CancellationToken.None, TaskCreationOptions.DenyChildAttach, taskScheduler).Unwrap().ContinueWith( t => { if (t.Exception != null) { reaction.ReportExceptionInReaction(t.Exception); } }, taskScheduler); } }, autorunOptions.ErrorHandler); } #pragma warning restore IDE0067 // Dispose objects before losing scope #pragma warning restore CA2000 // Dispose objects before losing scope void ReactionRunner() { if (reaction.IsDisposed) { return; } reaction.Track(() => expression(reaction)); } reaction.Schedule(); return(new DisposableDelegate(() => reaction.Dispose())); }