简体   繁体   English

如何用 observable 处理 async 方法抛出的异常?

[英]How to handle the exception thrown by the async method with observable?

I have an observable and I would like to subscribe this observable with an async method, however each time the exception thrown by the async method, the subscription disposed immediately even if I put the catch code in the observable definition.我有一个 observable,我想用异步方法订阅这个 observable,但是每次异步方法抛出异常时,即使我将 catch 代码放在 observable 定义中,订阅也会立即处理。 The pseudo code as follow to demonstrate this situation:下面的伪代码演示了这种情况:

[Fact]
public async Task Test()
{
    var observable = Observable.Create<int>(observer =>
    {
        try
        {
            Enumerable.Range(1, 10).ToList().ForEach(x =>
            {
                observer.OnNext(x);
            });
        }
        catch (Exception ex)
        {
           // get called after the exception is thrown 
            _testOutputHelper.WriteLine($"The exception is catch:{ex.ToString()}"); 
        }
        return Disposable.Create(() =>
        {
           // also get called after exception is thrown
            _testOutputHelper.WriteLine("Observable Dispose"); 
        });
    });

    Func<int, Task> handler = async (i) =>
     {
         // simulate the handler logic
         await Task.Delay(TimeSpan.FromSeconds(1));
         // throw the exception to test 
         throw new Exception($"{i}");
     };

    observable.Subscribe(x=>handler(x).Wait());

    await Task.Delay(TimeSpan.FromSeconds(10));
}

From above code, I don`t understand why the dispose delegate get called even the exception is catch (For some reason, I have to deal with the exception inside the observable definition), Is there any way to prevent the subscription being disposed when the exception thrown from async method?从上面的代码中,我不明白为什么即使捕获异常也会调用 dispose 委托(出于某种原因,我必须在可观察定义中处理异常),有没有办法防止订阅在异步方法抛出异常?

Your code is doing exactly what you told it to.您的代码完全按照您的要求执行。

The purpose of catching an exception is so that your program can continue without abruptly stopping.捕获异常的目的是让您的程序可以继续运行而不会突然停止。 That's exactly what your code is doing: the exception is caught, then execution continues after the catch block.这正是您的代码正在做的事情:异常被捕获,然后在catch块之后继续执行。

If you want it to do something else, you have two options.如果你想让它做其他事情,你有两个选择。

  1. Rethrow the exception after logging it:记录后重新抛出异常:
catch (Exception ex)
{
   // get called after the exception is thrown 
    _testOutputHelper.WriteLine($"The exception is catch:{ex.ToString()}");
    throw;
}

Then, whatever code called Test() will have the responsibility of catching that exception (or not).然后,任何称为Test()的代码都将负责捕获该异常(或不捕获)。

  1. Move the return inside the try block and return something else when the exception is caught:将 return 移到try块内,并在捕获到异常时返回其他内容:
try
{
    Enumerable.Range(1, 10).ToList().ForEach(x =>
    {
        observer.OnNext(x);
    });
    return Disposable.Create(() =>
    {
       // also get called after exception is thrown
        _testOutputHelper.WriteLine("Observable Dispose"); 
    });
}
catch (Exception ex)
{
   // get called after the exception is thrown 
    _testOutputHelper.WriteLine($"The exception is catch:{ex.ToString()}");
    
    return //something else
}

You may benefit from reading Microsoft's documentation on Exception Handling .您可能会受益于阅读 Microsoft 的异常处理文档。

What's happening in your code is a direct consequence of you using Observable.Create and filling the observable with this code:您的代码中发生的事情是您使用Observable.Create并使用以下代码填充 observable 的直接结果:

Enumerable.Range(1, 10).ToList().ForEach(x =>
{
    observer.OnNext(x);
});

Observable.Create uses the current thread to create the observable, so the Enumerable.Range(1, 10).ToList().ForEach executes immediately on the current thread and the call to OnNext executes the handler(x).Wait() immediately. Observable.Create使用当前线程创建 observable,因此Enumerable.Range(1, 10).ToList().ForEach立即在当前线程上执行,对OnNext的调用立即执行handler(x).Wait() .

You'll note, though, that the exception occurs in the delegate passed to the Subscribe .但是,您会注意到,异常发生在传递给Subscribe的委托中。 Internally there is code like this:内部有这样的代码:

catch (Exception exception)
{
    if (!autoDetachObserver.Fail(exception))
    {
        throw;
    }
    return autoDetachObserver;
}

That catches the exception in the subscribe, cancels the subscription - hence the "Observable Dispose" message - and then rethrows the exception and that's where your code catches it.这会在订阅中捕获异常,取消订阅 - 因此是"Observable Dispose"消息 - 然后重新抛出异常,这就是您的代码捕获它的地方。

Now, if you wanted to do this properly in Rx, you'd avoid Observable.Create .现在,如果您想在 Rx 中正确执行此操作,则应避免使用Observable.Create It's a tempting way to create observables, but it leads to trouble.这是一种创建 observables 的诱人方式,但它会带来麻烦。

Instead do this:而是这样做:

public async Task Test()
{
    Func<int, Task> handler = async (i) =>
     {
         // simulate the handler logic
         await Task.Delay(TimeSpan.FromSeconds(1));
         // throw the exception to test 
         throw new Exception($"{i}");
     };
 
    await
        Observable
            .Range(1, 10)
            .SelectMany(i => Observable.FromAsync(() => handler(i)))
            .LastOrDefaultAsync();
}

But, of course, we want to handle the exception.但是,当然,我们要处理异常。 The simple way is like this:简单的方法是这样的:

public async Task Test()
{
    Func<int, Task> handler = async (i) =>
     {
         // simulate the handler logic
         await Task.Delay(TimeSpan.FromSeconds(1));
         // throw the exception to test 
         throw new Exception($"{i}");
     };
 
    await
        Observable
            .Range(1, 10)
            .SelectMany(i =>
                Observable
                    .FromAsync(() => handler(i))
                    .Catch<Unit, Exception>(ex =>
                    {
                        Console.WriteLine($"The exception is catch:{ex.ToString()}");
                        return Observable.Empty<Unit>();
                    }))
            .LastOrDefaultAsync();
}

That now outputs the 10 exception errors and completes normally.现在输出 10 个异常错误并正常完成。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM