Pull to refresh

Comments 5

Наконец-то дождался статьи на тему команд в ReactiveUI, а то не так уж и много информации о них можно найти в сети (особенно учитывая куцую документацию). У синхронных Reactive-команд есть особенность, о которой в статье упомянуто, но не слишком широко. Речь про «глотание» ошибок, выброшенных в подписчике. В такой ситуации в приложении не происходит ровным счетом ничего, только кнопка перестает реагировать на команду, так как команда завершилась с ошибкой. Максимум что можно увидеть — скромную строчку Exception thrown: 'System.Exception' in ReactiveUI.Test.exe в логе при запуске с дебагом.
В итоге в подписчике синхронных команд крайне опасно вызывать методы, которые могут выкинуть исключения. Любое проскочившее необработанное исключение просто «убьет» команду, не выдав никакого уведомления пользователю, а именно не скрашив приложение в соответствии с принципом «Fail fast».
А ведь исключение может быть выброшено где угодно, в том числе и в методе, который был вызван в методе, который был вызван в методе ->… -> который был вызван в подписчике. Получается надо либо как параноику обертывать код подписчика в try-catch с самим Exception в catch-блоке чтобы иметь возможность хотя бы уведомить об ошибке, либо использовать синхронные ReactiveCommand для самых-самых простых вешей, которые уж точно не выкинут исключений.
Иногда из-за этой особенности приходилось эмулировать асинхронность при вызове быстрого синхронного кода:

CommandDoSomething = ReactiveCommand.CreateAsynctTask(() => { Код, который может выкинуть коленце });
CommandDoSomething.ThrownExceptions.Subscribe(...логирование либо уведомление...);
CommandDoSomething.Subscribe();


Иначе все возможные дефолтные проверки, такие как ArgumentNullException, IndexOutOfRangeException и прочие, при вызове методов из подписчика оказываются бесполезными.
Спасибо за развернутый комментарий! А вы не могли бы привести пример кода, в котором в ответ на исключение при вызове команда окончательно подыхает? В том примере, который есть в статье, код подписчика в Subscribe() бросает исключение, но повторный вызов Execute() возможен и CanExecute() возвращает true.

Реактивные команды реализованы так, что в команде как в IObservable не предполагается возможность возникновения OnError(), потому что тогда после первой же ошибки команда отваливалась бы и нужно было бы создавать ее снова. Каждый запуск команды внутри должен создавать новый IObservable, результат которого при успехе прилетает через саму команду. Если же что-то упало, то исключение либо проглотится (самый неприятный вариант), либо вылетит при вызове метода запуска команды, либо придет через ThrownExceptions. Сама же команда не должна пострадать.

Как ловить исключения в синхронных командах — хороший вопрос. По идее их просто не должно быть. Вот тут создатель фреймворка говорит, что сложный код и код, который бросает исключения — это не для синхронных команд и Subscribe().
In ReactiveUI, you should never put Interesting™ code inside the Subscribe block — Subscribe is solely to log the result of operations, or to wire up properties to other properties.
По-хорошему стоит использовать асинхронные команды для всего сколько-нибудь сложного и/или потенциально падающего. Для синхронных команд же можно попробовать сделать обертку над ReactiveCommand и реализовать Execute по-своему, чтобы каждый раз не оборачивать код в обработчик исключений, и команды громко падали в случае чего. Но я думаю, что в первую очередь стоит стремиться к тому, чтобы подписчики вообще не могли упасть.
Честно говоря, я несколько озадачен. Я использую ReactiveUI в контексте WPF-a и точно помню что при возникновении исключения в подписчике кнопка переставала вызывать команду. Однако, сейчас попробовал воспроизвести ситуацию, и кнопка продолжает нормально работать (вызывать подписчик с исключением) после первого нажатия. Возможно, я ошибаюсь и что-то не так раньше проверял :)
В любом случае, это тихое «проглатывание» исключения доставляет проблемы, так как часто нужно вызывать быструю (без запросов в сеть/базу данных и проч.) логику и это удобно делать через синхронную команду, без костылей в виде асинхронных команд.
В седьмой версии фреймворка обещают сильно изменить команды: сделать нормальную типизацию для синхронных команд, передача Action'a при создании синхронных команд и т.д. Но вот когда седьмая версия выйдет — хз. Никакой информации о дате релиза я так и не смог найти.
Думаю, скорее раньше было так, как вы описали, но сейчас поведение поменялось )

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

Можно, правда, схитрить и синхронные команды создавать асинхронными внутри. То есть вместо ReactiveCommand.Create() + command.Subscribe(action) вызывать что-то типа своего MySyncReactiveCommand.Create(action), который обернет ваш action в асинхронный вариант команды (получится синтаксис как в reactiveui 7). Оверхед на асинхронность остается, но он весь скрыт внутри и в случае чего не придется чистить все эти костыли во всех местах, где есть синхронные команды.

На релиз седьмой версии в ближайшее время, думаю, надеяться не стоит. Каких-то даже примерных сроков я не нашел, и судя по всему, еще есть приличный объем не сделанных задач.
Или для самых простых команд использовать стандартный ICommand…
Sign up to leave a comment.

Articles