public static IDisposable SubscribeWithContext <T>( this IObservable <T> source, Func <T, Task> action, ConcurrentExecutionMode mode = ConcurrentExecutionMode.AbortPrevious, TimeSpan?retryDelay = null) { var ctx = AsyncContext.Current; if (ctx == null) { throw new InvalidOperationException("This method can only be used in the scope of an AsyncContext."); } return(source .Execute(Execute, mode, ctx.Scheduler) .Retry(retryDelay ?? Constants.DefaultRetryDelay, ctx.Scheduler) .Subscribe()); Task Execute(CancellationToken ct, T value) => AsyncContext.Execute(ct, value, action); }
protected IDisposable At(TimeSpan timeOfDay, Func <DateTimeOffset, Task> operation, ConcurrentExecutionMode mode = ConcurrentExecutionMode.AbortPrevious) { var now = Scheduler.Now.LocalDateTime; var day = now.Date; if (now.TimeOfDay > timeOfDay) { day += TimeSpan.FromDays(1); } return(Observable .Timer(day + timeOfDay, TimeSpan.FromHours(24), Scheduler) .SubscribeWithContext(_ => operation(Scheduler.Now.ToLocalTime()), mode)); }
protected IDisposable At(IObservable <TimeSpan> timeOfDay, Func <DateTimeOffset, Task> operation, ConcurrentExecutionMode mode = ConcurrentExecutionMode.AbortPrevious) { return(timeOfDay .DistinctUntilChanged() .Select(tod => { var now = Scheduler.Now.LocalDateTime; var day = now.Date; if (now.TimeOfDay > tod) { day += TimeSpan.FromDays(1); } return Observable.Timer(day + tod, TimeSpan.FromHours(24), Scheduler); }) .Switch() .SubscribeWithContext(_ => operation(Scheduler.Now.ToLocalTime()), mode)); }
/// <summary> /// Executes an asynchronous action for each (depending of the <paramref name="mode"/>) value produced by an observable sequence /// </summary> /// <typeparam name="T">Type of the element in the source observable sequence</typeparam> /// <param name="source">The source observable sequence</param> /// <param name="action">Action to execute</param> /// <param name="mode"> /// Configures how to behave in case of a new element is produced by the <paramref name="source"/> /// while a previous execution of the <paramref name="action"/> is still pending. /// </param> /// <param name="scheduler">The scheduler to use to run <paramref name="action"/>.</param> /// <returns>An observable sequence of <see cref="Unit"/> which produce a value each time an execution of the action completes.</returns> public static IObservable <Unit> Execute <T>(this IObservable <T> source, Func <CancellationToken, T, Task> action, ConcurrentExecutionMode mode, IScheduler scheduler) { var originalAction = action; action = async(ct, t) => { try { await originalAction(ct, t); } catch (OperationCanceledException) { } }; switch (mode) { case ConcurrentExecutionMode.AbortPrevious: return(source .Select(d => Observable.FromAsync(ct => action(ct, d), scheduler)) .Switch()); case ConcurrentExecutionMode.Ignore: var running = 0; return(source .SelectMany(d => Observable.FromAsync( async ct => { if (Interlocked.CompareExchange(ref running, 1, 0) == 0) { try { await action(ct, d); } finally { running = 0; } } }, scheduler))); case ConcurrentExecutionMode.RunConcurrently: return(source.SelectMany(d => Observable.FromAsync(ct => action(ct, d), scheduler))); case ConcurrentExecutionMode.Queue: var @lock = new Utils.AsyncLock(); return(source .SelectMany(d => Observable.FromAsync( async ct => { using (await @lock.LockAsync(ct)) { await action(ct, d); } }, scheduler))); default: throw new ArgumentOutOfRangeException(nameof(mode)); } }