What is the optimal way to perform a delayed sampling with Rx?

Viewed 233

I'm working on a Xamarin app where I have extended the Connectivity plugin to use Rx rather than events.

My goal was to introduce a slight delay when connectivity is re-established to allow the network adapter time to connect to the internet (workaround for UWP). If any values appear within this delay, only the last one needs to be kept since only the current connection state matters.

This works fine, but it feels a bit hacky:

internal static class ConnectivityExtensions
{
    public static IObservable<bool> ToObservable(this IConnectivity @this)
    {
        var connectivity = Observable
            .FromEventPattern<ConnectivityChangedEventHandler, ConnectivityChangedEventArgs>(
                handler => @this.ConnectivityChanged += handler,
                handler => @this.ConnectivityChanged -= handler)
            .Select(args => args.EventArgs.IsConnected);

        var sampling = connectivity
            .Timestamp()
            .Select(ts => new
            {
                ts.Value,
                ts.Timestamp,
                // If reconnection, delay subscriber notification for 250ms
                DelayUntil = ts.Value ? (DateTimeOffset?)ts.Timestamp.Add(TimeSpan.FromMilliseconds(250)) : null
            })
            .Scan((acc, current) => new
            {
                current.Value,
                current.Timestamp,
                // If current notification is during reconnection notification delay period, delay the current notification too
                DelayUntil = current.Timestamp < acc.DelayUntil ? acc.DelayUntil : current.DelayUntil
            })
            // Perform reconnection delay
            .Delay(x => x.DelayUntil.HasValue
                ? Observable.Return(x.DelayUntil.Value).Delay(x.DelayUntil.Value)
                : Observable.Empty<DateTimeOffset>())
            // All delayed notifications are delayed until the same time, so we only need one notification to trigger sampling after delay
            .DistinctUntilChanged()
            .Select(_ => Unit.Default);

        return connectivity
            .Sample(sampling)
            .StartWith(@this.IsConnected)
            .DistinctUntilChanged()
            .Replay(1)
            .RefCount();
    }
}

Example Output:

With this example, you can hopefully see the benefit of what I'm doing. The delay prevents subscribers from processing data when "connectivity" changes, but internet connection has not been established (UWP). Additionally, this also protects subscribers from any quick "on/off" notifications.

                                                                                     // StartsWith: True
ConnectivityChanged(null, new ConnectivityChangedEventArgs { IsConnected = true });  // Delay 250ms
Thread.Sleep(TimeSpan.FromMilliseconds(250));                                        // Sample: True, Ignored due to DistinctUntilChanged
ConnectivityChanged(null, new ConnectivityChangedEventArgs { IsConnected = false }); // Sample: False
Thread.Sleep(TimeSpan.FromMilliseconds(250));
ConnectivityChanged(null, new ConnectivityChangedEventArgs { IsConnected = true });  // Delay 250ms, Discarded due to Sample
ConnectivityChanged(null, new ConnectivityChangedEventArgs { IsConnected = false }); // Delayed by previous, Discarded due to Sample
ConnectivityChanged(null, new ConnectivityChangedEventArgs { IsConnected = true });  // Delayed by previous, Discarded due to Sample
ConnectivityChanged(null, new ConnectivityChangedEventArgs { IsConnected = false }); // Delayed by previous
Thread.Sleep(TimeSpan.FromMilliseconds(250));                                        // Sample: False, Ignored due to DistinctUntilChanged
ConnectivityChanged(null, new ConnectivityChangedEventArgs { IsConnected = true });  // Delay 250ms, Discarded due to Sample
ConnectivityChanged(null, new ConnectivityChangedEventArgs { IsConnected = false }); // Delayed by previous
Thread.Sleep(TimeSpan.FromMilliseconds(250));                                        // Sample: False, Ignored due to DistinctUntilChanged

// Final Output:
// True
// False

Is there a more optimal way to achieve this type of delayed sampling?

2 Answers
Related