Как ReactiveUI обрабатывает исключения при выполнении подчиненных ReactiveCommands?

Вот (чрезмерно) упрощенная версия того, что я пытаюсь продемонстрировать:

var reactiveCommandA = ReactiveCommand.CreateAsyncTask(_ => CanPossiblyThrowAsync());
reactiveCommandA.ThrownExceptions
                .Subscribe(ex => UserError.Throw("Oh no A", ex));

var reactiveCommandB = ReactiveCommand.CreateAsyncTask(_ => CanAlsoPossiblyThrowAsync());
reactiveCommandB.ThrownExceptions
                .Subscribe(ex => UserError.Throw("Oh no B", ex));

var reactiveCommandC = ReactiveCommand.CreateAsyncTask
   (
     async _ =>
               {
                 await reactiveCommandA.ExecuteAsync(); // <= Could throw here
                 await reactiveCommandB.ExecuteAsync();
                 DoSomethingElse();
               }
    );

reactiveCommandC.ThrownExceptions
                .Subscribe(ex => UserError.Throw("Oh no C", ex));

Поэтому предположим, что моя фоновая реализация для reactiveCommandA может вызвать исключение. Это нормально, так как я подписался на .ThrownExceptions и теоретически буду уведомлять пользователя и повторять/неудачно/прервать (здесь не показано для краткости). Так что до диспетчера не доберется.

Так что это здорово, когда reactiveCommandA выполняется сам по себе. Однако у меня есть reactiveCommandC, который выполняет reactiveCommandA и reactiveCommandB. Я также подписываюсь на его .ThrownExceptions. Проблема, с которой я сталкиваюсь, заключается в том, что если я выполняю броски реализации reactiveCommandC и reactiveCommandA внутри него, это также приводит к взрыву reactiveCommandC. Затем я дважды уведомляю пользователя об одной и той же корневой ошибке, потому что reactiveCommandA делает свою .ThrownExceptions вещь, а reactiveCommandC делает свою .ThrownExceptions вещь.

Так есть ли стандартный подход к такого рода ситуации? Предпочтительно что-то несколько элегантное, так как я нахожу существующий код довольно чистым, и я не хочу загромождать его или добавлять спагетти.

Вещи, о которых я подумал:

  • Окружение строки «ожидание...» блоком try/catch, проглатывание исключения и выход. Кажется некрасивым, если мне приходится делать это много.

  • Использование await reactiveCommandA.ExecuteAsync().Catch(Observable.Never<Unit>());, хотя я думаю, что это приведет к тому, что reactiveCommandC никогда не завершится, поэтому он никогда не сможет выполниться снова.

  • Используя тот же подход с методом .Catch(), но возвращая логическое значение в зависимости от того, успешно я это сделал или нет (например, .Catch(Observable.Return(false)). Все равно придется проверять, можем ли мы продолжить между каждым оператором await.

Здесь можно сделать что-нибудь похлеще? Спасибо.


person Jon Comtois    schedule 06.10.2014    source источник


Ответы (2)


Хм, это аспект дизайна ReactiveCommand, который отстой (раскрытие: я могу сказать, что это отстой, я это написал!) Вот ленивый способ исправить это:

Observable.Merge(rxCmdA.ThrownExceptions, rxCmdB.ThrownExceptions, rxCmdC.ThrownExceptions)
    .Throttle(TimeSpan.FromMilliseconds(250), RxApp.MainThreadScheduler)
    .Subscribe(ex => UserError.Throw("Oh no C", ex));

В будущем я определенно хочу исправить некоторые из этих вещей с двойным броском, а также несколько других вещей, связанных с дизайном RxCmd.

person Ana Betts    schedule 06.10.2014
comment
Справедливо, Пол. Хотя это и не очень удовлетворительный ответ, я рад, что он находится на вашем радаре для будущих улучшений, и я также рад узнать, что не упустил ничего очевидного. Я буду продолжать наблюдать за развитием RxUI. - person Jon Comtois; 07.10.2014

Я немного опоздал на вечеринку, но как насчет чего-то подобного?

bool shouldThrow = true;

var reactiveCommandA = ReactiveCommand.CreateAsyncTask(_ => CanPossiblyThrowAsync());
reactiveCommandA.ThrownExceptions
                .Where(_ => shouldThrow)
                .Subscribe(ex => UserError.Throw("Oh no A", ex));

var reactiveCommandB = ReactiveCommand.CreateAsyncTask(_ => CanAlsoPossiblyThrowAsync());
reactiveCommandB.ThrownExceptions
                .Where(_ => shouldThrow)
                .Subscribe(ex => UserError.Throw("Oh no B", ex));

var reactiveCommandC = ReactiveCommand.CreateAsyncTask
   (
     async _ =>
               {
                 shouldThrow = false;

                 try
                 {
                     await reactiveCommandA.ExecuteAsync(); // <= Could throw here
                     await reactiveCommandB.ExecuteAsync();
                 }

                 finally
                 {
                     shouldThrow = true;
                 }
                 DoSomethingElse();
               }
    );

reactiveCommandC.ThrownExceptions
                .Subscribe(ex => UserError.Throw("Oh no C", ex));

Может быть, не супер элегантно, но в теории должно работать

person Flagbug    schedule 17.10.2014
comment
Спасибо, я тоже думал о чем-то подобном, хотя теперь это флаг, который мне нужно не забыть установить. Это может быть исправлением в простых сценариях, таких как пример, но становится громоздким, если у меня много переплетенных команд. - person Jon Comtois; 17.10.2014