C# Rx.Net+;反应式用户界面&x2B;马哈普斯地铁-重复和;使用选通对话框重试异步请求

C# Rx.Net+;反应式用户界面&x2B;马哈普斯地铁-重复和;使用选通对话框重试异步请求,c#,system.reactive,reactiveui,mahapps.metro,rx.net,C#,System.reactive,Reactiveui,Mahapps.metro,Rx.net,给定一个可观察的形式: var fetchTimer=Observable.Timer(TimeSpan.FromSeconds(1)); var状态获取程序= Observable.FromAsync(async()=>Wait _robotsClient.GetRobotsStateAync(new GetRobotsStateRequest()); var delayedFetch=fetchTimer.SelectMany(stateFetcher); 这提供了在延迟后获取状态的方法

给定一个可观察的形式:

var fetchTimer=Observable.Timer(TimeSpan.FromSeconds(1));
var状态获取程序=
Observable.FromAsync(async()=>Wait _robotsClient.GetRobotsStateAync(new GetRobotsStateRequest());
var delayedFetch=fetchTimer.SelectMany(stateFetcher);
这提供了在延迟后获取状态的方法

修改可以定期执行此操作:

var regularFetch=Observable.Interval(TimeSpan.FromSeconds(5)).Select(=>stateFetcher.Switch();
这将每5秒请求一个值

但是,请求可能会失败(远程服务无法访问等);考虑到这一点,尝试生成重试操作的机制以及提醒用户的钩子可能会很棘手

-我提出的这一问题涵盖了解决这一问题的初步方法,以及部分尝试/部分解决方案


在这里,我想分享我得出的解决方案。

我们可以将问题总结如下:

  • 我们有一个可观测的源,它可以产生一个错误值
  • 如果发生错误,我们需要做些什么
  • 从第一个错误开始,我们希望使用通知进行多次尝试
  • 如果这些尝试都失败了,我们想做点什么
  • 在流程结束时,我们希望重新启动所有流程
  • 因此:

    错误
    ->
    初始对话框
    ->
    每次尝试时通知重试
    ->
    再次执行所有操作

    在整个过程中的任何一点上,成功的价值排放都应该绕过一切,并流回

    考虑到这种高度固执己见的方法,下面是我创建的实用程序:

    公共静态IObservable with GatedRetry(
    这是一个可观测的来源,
    int retriesPerCycle,
    Func onInitialFailure,
    函数retryNotificationBlock,
    函数(失败循环)
    {
    IObservable GetInitialHandler(异常e)=>
    可观察的。FromAsync(()=>onInitialFailure(e))
    .选择(=>(T)默认值);
    IObservable GetCycleFailureHandler(异常e)=>
    可观察的。FromAsync(()=>onFailedCycle(e))
    .选择(=>(T)默认值);
    IObservable GetRetryFlow()=>
    可观察。创建(异步子=>
    {
    var尝试=1;
    Func disposeCallback=()=>Task.CompletedTask;
    var通知程序=Wait retryNotificationBlock(dc=>
    {
    disposeCallback=dc;
    });
    等待通知程序(null,1);
    返回
    来源
    .做(
    _ =>
    {
    },
    异步(异常e)=>
    {
    如果(尝试+1
    {
    if(disposeCallback!=null)
    {
    等待处置回拨();
    }
    })
    .订阅(
    val=>{sub.OnNext(val);sub.OnCompleted();},
    (例外情况e)=>{sub.OnError(e);}
    );
    });
    IObservable GetCycleFlow()=>
    GetRetryFlow()
    .Catch((异常e)=>
    GetCycleFailureHandler(e)
    .Select(=>GetCycleFlow())
    .Switch()
    )
    .重试();
    IObservable GetPrimaryFlow()=>
    来源
    .Catch((异常e)=>GetInitialHandler(e))
    .选择(val=>
    EqualityComparer.Default.Equals(val,默认值)
    ?GetCycleFlow()。选择(=>GetPrimaryFlow())。开关()
    :GetPrimaryFlow().StartWith(val)
    )
    .开关();
    返回GetPrimaryFlow();
    }
    
    我完全承认这可能不是最好的方法,在通知块中(对于每次重试尝试)有一点回调内部回调的混乱,以便在重试“周期”完成后(成功或其他)支持清理

    用法如下:

    var-latestState=
    可观察。SelectMany(fetchTimer、stateFetcher)
    .用GatedRetry(
    3.
    异步ex=>
    {
    //显示初始错误对话框
    },
    异步(disperhook)=>
    {
    //显示“尝试重试”对话框
    disperhook(异步()=>
    {
    //关闭“尝试重试”对话框
    //做任何清理工作
    });
    返回异步(异常ex,int尝试)=>
    {
    //更新对话框
    //ex是刚刚完成的尝试产生的异常
    };
    },
    异步ex=>
    {
    //显示“我们仍然不能完全理解”对话框
    //此任务完成后,整个过程将再次启动
    }
    )
    .Publish();
    
    这种方法允许定制挂钩点,并按预期传递成功值

    事实上,下游订阅者应该只在成功提供一个值时看到一个值——他们也不应该看到一个错误,因为它位于无限重试中


    与原始问题中的解决方案相比,这使用了
    Select
    +
    开关
    ,而不是
    SelectMany
    ,以确保内部观察值被正确地取消订阅和处理。

    只是一个旁注,不要将
    async
    /
    wait
    放在
    fromsync
    调用中。是否有任何错误伊森:
    。重试(…)
    对你不起作用?@Enigmativity噢?为什么
    异步/等待
    这件事对我来说很有效?在我现在得到的解决方案中,最大的问题是我把计时器和它捆绑在一起,当然它在对话框可见时发出,这导致了问题。
    可见。fromsync
    打开e
    任务
    已经完成。