如何在SelectMany语句中处理异步方法的异常

时间:2012-03-05 22:26:03

标签: c# .net .net-4.0 system.reactive

我正在尝试使用Rx异步处理某些任务,例如

var list = Enumerable.Range(0, 100)
    .ToObservable()
    .SelectMany(x => Observable.Start(() => {
        Console.WriteLine("Processing {0} ...", x);

        Thread.Sleep(100 * x % 3);

        if (x > 90) {
            Console.WriteLine("Procesing exception {0} > 90", x);
            throw new Exception("Value too large");
        }
        Console.WriteLine("Processing {0} completed.", x);
        return x;
    }))
    .Subscribe(
        x => { Console.WriteLine("Next [{0}]", x); },
        e => {
            Console.WriteLine("Exception:");
            Console.WriteLine(e.Message);
        },
        () => { Console.WriteLine("Complete"); }
    );

我对此代码的问题是异常未传递给订阅者。所以,经过大量的尝试,我放弃了,并决定问这个简单的问题:

如何处理SelectMany语句中异步方法中引发的异常?

为了说清楚,最终的实现是一个同步的函数调用,可能会也可能不会抛出异常。目标是将其传递给订户,以便可以进一步处理(在特定情况下,将向用户显示消息)。

修改

我将我的发现归结为一个答案,以便我可以将这个问题标记为已回答。就个人而言,我不同意自我回答......但有时没有别的办法,所以很抱歉。

2 个答案:

答案 0 :(得分:3)

使用Materialize将OnError / OnCompleted消息转换为通知。

例如,

observable.SelectMany(x => Observable.Start(fn).Materialize())

将在您的实际订阅点中向您提供包含在通知中的错误/完成,而不是在SelectMany中终止错误。

这对大多数异步调用操作很有用,因为该方法失败或完成。

答案 1 :(得分:1)

答案

实际上代码 正常工作。但是,由于异步操作仍在后台执行,调试器会在例外情况下中断 - 至少是在第一个异常发生时已经启动的那些操作。扔了我!如果您在没有调试器的情况下运行代码,则会吞下异常。所以我猜问题实际上是在计算机前: - )

正如我所假设的那样Observable.Start仍然有一些澄清 - 这是正确的 - 实现应该实际上实现了一些错误处理...参见背景。

<强>背景

Observable.Start是一种便捷方法,它使用Observable.ToAsync方法将函数/动作转换为异步操作。如果你看一下该方法的实现,你会发现它已经进行了异常处理/转发。

public static Func<IObservable<TResult>> ToAsync<TResult>(this Func<TResult> function, IScheduler scheduler) {
    if (function != null) {
        if (scheduler != null) {
            return () => {
                AsyncSubject<TResult> asyncSubject = new AsyncSubject<TResult>();
                scheduler.Schedule(() => {
                    TResult result = default(TResult);
                    try {
                        result = function();
                    } catch (Exception exception1) {
                        Exception exception = exception1;
                        asyncSubject.OnError(exception);
                        return;
                    }
                    asyncSubject.OnNext(result);
                    asyncSubject.OnCompleted();
                });
                return asyncSubject.AsObservable<TResult>();
            };
        } else {
            throw new ArgumentNullException("scheduler");
        }
    } else {
        throw new ArgumentNullException("function");
    }
}