/// <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);
     });
 }
Example #2
0
        /// <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()));
        }