using System; using System.Threading; using System.Threading.Tasks; // ReSharper disable UnusedMember.Global - public API namespace IPCamLib { /// /// Receives frames from an ICamera as well as status updates. /// public interface IRetryableCameraObserver : ICameraObserver { /// /// Called when a new Fetch attempt has started. /// Task OnFetch(); /// /// Called when the stream was disconnected. /// /// Contains the exception, if any occured. Null for graceful disconnects and timeouts. /// The delay until the next Fetch is attempted. Task OnDisconnected(Exception exception, TimeSpan retryDelay); } /// /// Implements retry logic for an ICamera instance. /// public class RetryableCamera { private readonly ICamera camera; /// /// Creates a new instance of a RetryableCamera. /// /// The camera instance to fetch from. public RetryableCamera(ICamera camera) { this.camera = camera; } /// /// Starts receiving frames and will retry when disconnected. /// /// The observer implementation to receive frames and status updates. /// A CancellationToken which will stop the camera stream when cancelled. public async Task Fetch(IRetryableCameraObserver observer, CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) { // TODO incremental back-off? var retryDelay = TimeSpan.FromSeconds(5); var exception = false; try { await observer.OnFetch(); await camera.Fetch(observer, cancellationToken); } catch (TaskCanceledException) { // Empty by design } catch (Exception e) { await observer.OnDisconnected(e, retryDelay); exception = true; } if (!exception && !cancellationToken.IsCancellationRequested) await observer.OnDisconnected(null, retryDelay); await Task.Delay(retryDelay, cancellationToken); } } } }