簡體   English   中英

如何用 observable 處理 async 方法拋出的異常?

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

我有一個 observable,我想用異步方法訂閱這個 observable,但是每次異步方法拋出異常時,即使我將 catch 代碼放在 observable 定義中,訂閱也會立即處理。 下面的偽代碼演示了這種情況:

[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));
}

從上面的代碼中,我不明白為什么即使捕獲異常也會調用 dispose 委托(出於某種原因,我必須在可觀察定義中處理異常),有沒有辦法防止訂閱在異步方法拋出異常?

您的代碼完全按照您的要求執行。

捕獲異常的目的是讓您的程序可以繼續運行而不會突然停止。 這正是您的代碼正在做的事情:異常被捕獲,然后在catch塊之后繼續執行。

如果你想讓它做其他事情,你有兩個選擇。

  1. 記錄后重新拋出異常:
catch (Exception ex)
{
   // get called after the exception is thrown 
    _testOutputHelper.WriteLine($"The exception is catch:{ex.ToString()}");
    throw;
}

然后,任何稱為Test()的代碼都將負責捕獲該異常(或不捕獲)。

  1. 將 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
}

您可能會受益於閱讀 Microsoft 的異常處理文檔。

您的代碼中發生的事情是您使用Observable.Create並使用以下代碼填充 observable 的直接結果:

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

Observable.Create使用當前線程創建 observable,因此Enumerable.Range(1, 10).ToList().ForEach立即在當前線程上執行,對OnNext的調用立即執行handler(x).Wait() .

但是,您會注意到,異常發生在傳遞給Subscribe的委托中。 內部有這樣的代碼:

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

這會在訂閱中捕獲異常,取消訂閱 - 因此是"Observable Dispose"消息 - 然后重新拋出異常,這就是您的代碼捕獲它的地方。

現在,如果您想在 Rx 中正確執行此操作,則應避免使用Observable.Create 這是一種創建 observables 的誘人方式,但它會帶來麻煩。

而是這樣做:

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();
}

但是,當然,我們要處理異常。 簡單的方法是這樣的:

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();
}

現在輸出 10 個異常錯誤並正常完成。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM