Search code examples
c#reactiveui

How to catch exceptions in ReactiveCommand with async method


I have a problem which is very similar to this one. However, in my case I create a ReactiveCommand that calls an async method on execution. The ThrownExceptions observable doesn't seem to pipe any exceptions no matter where they are thrown (directly in the method or in the task started by it).

I have a written a minimum example to demonstrate that. I know that ThrownExceptions doesn't catch everything but I don't know for which cases it is not designed to work or how to handle these exceptions correctly.

using ReactiveUI;
using System;
using System.Reactive;
using System.Threading.Tasks;

namespace ConsoleApp3
{
    class Program
    {
        static void Main()
        {
            var test = new TestClass();
   
            while (Console.ReadKey().Key == ConsoleKey.Enter)
            {
                test.Command.Execute().Subscribe();
            }

            Console.ReadKey();
        }
    }


    public class TestClass
    {
        public TestClass()
        {
            Command = ReactiveCommand.Create(() => RunCommand());
            Command.ThrownExceptions.Subscribe(ex => HandleException(ex));
        }
   
        public ReactiveCommand<Unit, Task> Command { get; private set; }

        private async Task RunCommand()
        {
            //throw new Exception("will not be handled");

            //await Task.Run(() => throw new Exception("will also not be handled"));
        }

        private void HandleException(Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

Solution

  • To answer my own question: commands that call async methods have to be created using the CreateFromTask method instead of the Create method.

    In addition, the exceptions are not caught if the command is executed by subscribing to it's Execute observable. The Execute method of the ICommand interface has to be used instead (commands should be exposed to the public using this interface anyway).

    I have changed my demo project as below:

    class Program
    {
        static void Main()
        {
            var test = new TestClass();
       
            while (Console.ReadKey().Key == ConsoleKey.Enter)
            {
                (test.Command as ICommand).Execute(null);
            }
    
            Console.ReadKey();
        }
    }
    
    
    public class TestClass
    {
        public TestClass()
        {
            Command = ReactiveCommand.CreateFromTask(RunCommand);
            Command.ThrownExceptions.Subscribe(ex => HandleException(ex));
        }
       
        public ReactiveCommand<Unit, Unit> Command { get; private set; }
    
        private async Task RunCommand()
        {
            //throw new Exception("will be handled");
    
            await Task.Run(() => 
            {
                throw new Exception("will also be handled");
            });
        }
    
        private void HandleException(Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }