169 lines
6.2 KiB
C#
169 lines
6.2 KiB
C#
// Licensed to the .NET Foundation under one or more agreements.
|
|
// The .NET Foundation licenses this file to you under the MIT License.
|
|
// See the LICENSE file in the project root for more information.
|
|
|
|
using System.Reactive.Disposables;
|
|
using System.Runtime.CompilerServices;
|
|
|
|
namespace System.Reactive.Concurrency
|
|
{
|
|
internal sealed class CatchScheduler<TException> : SchedulerWrapper
|
|
where TException : Exception
|
|
{
|
|
private readonly Func<TException, bool> _handler;
|
|
|
|
public CatchScheduler(IScheduler scheduler, Func<TException, bool> handler)
|
|
: base(scheduler)
|
|
{
|
|
_handler = handler;
|
|
}
|
|
|
|
protected override Func<IScheduler, TState, IDisposable> Wrap<TState>(Func<IScheduler, TState, IDisposable> action)
|
|
{
|
|
return (self, state) =>
|
|
{
|
|
try
|
|
{
|
|
return action(GetRecursiveWrapper(self), state);
|
|
}
|
|
catch (TException exception) when (_handler(exception))
|
|
{
|
|
return Disposable.Empty;
|
|
}
|
|
};
|
|
}
|
|
|
|
public CatchScheduler(IScheduler scheduler, Func<TException, bool> handler, ConditionalWeakTable<IScheduler, IScheduler> cache)
|
|
: base(scheduler, cache)
|
|
{
|
|
_handler = handler;
|
|
}
|
|
|
|
protected override SchedulerWrapper Clone(IScheduler scheduler, ConditionalWeakTable<IScheduler, IScheduler> cache)
|
|
{
|
|
return new CatchScheduler<TException>(scheduler, _handler, cache);
|
|
}
|
|
|
|
protected override bool TryGetService(IServiceProvider provider, Type serviceType, out object? service)
|
|
{
|
|
service = provider.GetService(serviceType);
|
|
|
|
if (service != null)
|
|
{
|
|
if (serviceType == typeof(ISchedulerLongRunning))
|
|
{
|
|
service = new CatchSchedulerLongRunning((ISchedulerLongRunning)service, _handler);
|
|
}
|
|
else if (serviceType == typeof(ISchedulerPeriodic))
|
|
{
|
|
service = new CatchSchedulerPeriodic((ISchedulerPeriodic)service, _handler);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private class CatchSchedulerLongRunning : ISchedulerLongRunning
|
|
{
|
|
private readonly ISchedulerLongRunning _scheduler;
|
|
private readonly Func<TException, bool> _handler;
|
|
|
|
public CatchSchedulerLongRunning(ISchedulerLongRunning scheduler, Func<TException, bool> handler)
|
|
{
|
|
_scheduler = scheduler;
|
|
_handler = handler;
|
|
}
|
|
|
|
public IDisposable ScheduleLongRunning<TState>(TState state, Action<TState, ICancelable> action)
|
|
{
|
|
// Note that avoiding closure allocation here would introduce infinite generic recursion over the TState argument
|
|
|
|
return _scheduler.ScheduleLongRunning(
|
|
state,
|
|
(state1, cancel) =>
|
|
{
|
|
try
|
|
{
|
|
action(state1, cancel);
|
|
}
|
|
catch (TException exception) when (_handler(exception))
|
|
{
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
private sealed class CatchSchedulerPeriodic : ISchedulerPeriodic
|
|
{
|
|
private sealed class PeriodicallyScheduledWorkItem<TState> : IDisposable
|
|
{
|
|
private SingleAssignmentDisposableValue _cancel;
|
|
private bool _failed;
|
|
|
|
private readonly Func<TState, TState> _action;
|
|
private readonly CatchSchedulerPeriodic _catchScheduler;
|
|
|
|
public PeriodicallyScheduledWorkItem(CatchSchedulerPeriodic scheduler, TState state, TimeSpan period, Func<TState, TState> action)
|
|
{
|
|
_catchScheduler = scheduler;
|
|
_action = action;
|
|
|
|
// Note that avoiding closure allocation here would introduce infinite generic recursion over the TState argument
|
|
_cancel.Disposable = scheduler._scheduler.SchedulePeriodic(state, period, state1 => Tick(state1).state);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_cancel.Dispose();
|
|
}
|
|
|
|
private (PeriodicallyScheduledWorkItem<TState> @this, TState state) Tick(TState state)
|
|
{
|
|
//
|
|
// Cancellation may not be granted immediately; prevent from running user
|
|
// code in that case. Periodic schedulers are assumed to introduce some
|
|
// degree of concurrency, so we should return from the SchedulePeriodic
|
|
// call eventually, allowing the d.Dispose() call in the catch block to
|
|
// take effect.
|
|
//
|
|
if (_failed)
|
|
{
|
|
return default;
|
|
}
|
|
|
|
try
|
|
{
|
|
return (this, _action(state));
|
|
}
|
|
catch (TException exception)
|
|
{
|
|
_failed = true;
|
|
|
|
if (!_catchScheduler._handler(exception))
|
|
{
|
|
throw;
|
|
}
|
|
|
|
_cancel.Dispose();
|
|
return default;
|
|
}
|
|
}
|
|
}
|
|
|
|
private readonly ISchedulerPeriodic _scheduler;
|
|
private readonly Func<TException, bool> _handler;
|
|
|
|
public CatchSchedulerPeriodic(ISchedulerPeriodic scheduler, Func<TException, bool> handler)
|
|
{
|
|
_scheduler = scheduler;
|
|
_handler = handler;
|
|
}
|
|
|
|
public IDisposable SchedulePeriodic<TState>(TState state, TimeSpan period, Func<TState, TState> action)
|
|
{
|
|
return new PeriodicallyScheduledWorkItem<TState>(this, state, period, action);
|
|
}
|
|
}
|
|
}
|
|
}
|