我有以下情况:
IObservable<E>
E
以获取E1
或错误状态,在这种情况下我需要错误消息M1
E1
以获取E2
或错误消息M2
还有一个额外的复杂因素,即结果En和/或错误消息Mn
可能依赖于所有值E
,E1
,...,En-1
- 不仅在En-1
。
鉴于这一切,是否有比我使用的更好的模式?
[编辑]根据要求,我添加了一个完整的例子;不幸的是,这个帖子非常大。
internal class Program
{
private static void Main()
{
var stream = Enumerable.Range(1, 10).Select(i => new Record { Id = i }).ToObservable();
stream
.Select(it => new ComplexType { Item = it })
.SelectIfOk(Process1)
.SelectIfOk(Process2)
.SelectIfOk(ProcessN)
.Subscribe(DisplayResult);
Console.ReadLine();
}
private static ComplexType Process1(ComplexType data)
{
// do some processing
data.E1 = data.Item.Id * 10;
// check for errors in output
if (data.E1 == 30 || data.E1 == 70)
{
data.Errors.Add("Error");
}
return data;
}
private static ComplexType Process2(ComplexType data)
{
// do some processing
data.E2 = (data.E1 - 3).ToString();
// check for errors in output
// can generate multiple errors for the same item
if (data.E2.StartsWith("4"))
{
// does not only depend on the immediate precursor, E1 in this case
data.Errors.Add("Starts with 4 -- " + data.Item.Id);
}
if (data.E2.StartsWith("8"))
{
data.Errors.Add("Starts with 8");
}
return data;
}
private static ComplexType ProcessN(ComplexType data)
{
// do some processing
data.EN = "Success " + data.E2;
// this one doesn't generate errors
return data;
}
private static void DisplayResult(ComplexType data)
{
if (data.Errors.Any())
{
Console.WriteLine("{0:##0} has errors: " + string.Join(",", data.Errors));
}
else
{
Console.WriteLine("{0:##0}: {1}", data.Item.Id, data.EN);
}
}
}
这些是上面代码示例中使用的类:
public class Record
{
public int Id { get; set; }
public string FullName { get; set; }
public string OtherStuff { get; set; }
}
public class ComplexType
{
public Record Item { get; set; }
// intermediary results
public int E1 { get; set; }
public string E2 { get; set; }
// final result
public string EN { get; set; }
public List<string> Errors { get; set; }
public ComplexType()
{
Errors = new List<string>();
}
}
请注意,E1
,E2
,...,En
的类型之间没有任何关系(特别是,它们都不会继承相同的常见类型)。
SelectIfOk
是一种扩展方法:
public static IObservable<T> SelectIfOk<T>(this IObservable<T> observable,
Func<T, T> selector)
where T : ComplexType
{
return observable.Select(item => item.Errors.Any() ? item : selector(item));
}
运行此代码的结果是:
1: Success 7
2: Success 17
3 has errors: Error
4: Success 37
5 has errors: Starts with 4 -- 5
6: Success 57
7 has errors: Error
8: Success 77
9 has errors: Starts with 8
10: Success 97
我正在使用ComplexType,因此我可以同时携带中间结果和错误状态,它只是看起来很可疑。我已经盯着那段代码一周了(这是一个爱好项目),我一直觉得我错过了用Rx做事的正确方法。
[编辑]我忘了提到一个非常重要的事情:我必须处理流中的所有项,即使其中一些产生错误;这就是为什么我不能只使用带有异常的Subscribe
重载 - 它将完成流。当出现错误时放弃一个项目(如果Process1
生成错误,则Process2
,...,ProcessN
不再执行),但不会放弃整个流。
[编辑]另一个澄清:如果它有帮助,我想到的处理将更自然地适合TPL DataFlow库,除了我限于.NET 4.0所以我不能使用它
顺便说一下,我一直无法在Rx中找到任何关于错误处理的认真讨论,通常会提到Subscribe
重载/ OnError
来电,这就是它。有没有人建议对该主题进行深入研究?
答案 0 :(得分:0)
提前了解所有ProcessN
意味着您可以摆脱多个Select
运算符,简化查询并揭示您的真实意图。
stream.Select(e => Process(e));
...
? Process(E e)
{
// Process1, Process2, ...ProcessN
}
现在我们看到这不是一个被动的问题。这更像是一个交互式聚合问题。
您还没有提到您最终需要在订阅中查看哪种输出,但我只是假设它是流程的汇总结果。
要定义返回类型,我们首先需要定义ProcessN
的返回类型。而不是使用你的ComplexType
,我现在将使用具有更好语义的类型:
Either<E, Exception> Process(?);
因此,每个ProcessN
函数都可以返回E
或Exception
(不投掷)。
此外,根据您的要求,每个ProcessN
必须接收正在运行的聚合的当前结果作为其输入。所以就像你上面的定义一样,我们必须更换?以及在它之前调用的ProcessN
函数的返回值列表。
Either<E, Exception> Process(IList<Either<E, Exception>> results);
现在我们可以定义聚合器的返回类型(如上所述):
IList<Either<E, Exception>> Process(E e)
{
// Process1, Process2, ...ProcessN
}
聚合器的主体可以按如下方式实现:
IList<Either<E, Exception>> Process(E e)
{
var results = new List<Either<E, Exception>>();
results.Add(Process1(results.AsReadOnly()));
results.Add(Process2(results.AsReadOnly()));
...
results.Add(ProcessN(results.AsReadOnly()));
return results.AsReadOnly();
}
首先阅读Rx Design Guidelines。
Intro to Rx有一个关于错误处理运算符的部分。
以下是有关Rx中错误处理语义和合同的一些深入讨论/评论:
(完全披露:我为所有这些特别的讨论做出了贡献。)
答案 1 :(得分:0)
当我问这个问题时,我不知道Rx中的Notification<T>
类和Materialize
方法。这是我提出的解决方案 - 它主要解决了#34;管道和#34;问题的方面,但我可以解决&#34;取决于中间结果&#34;使用元组的方面:
private static void Main()
{
var source = new Subject<int>();
source
.Materialize()
.SelectIfOk(Process1)
.SelectIfOk(Process2)
.Subscribe(it =>
Console.WriteLine(it.HasValue
? it.Value.ToString()
: it.Exception != null ? it.Exception.Message : "Completed."));
source.OnNext(1);
source.OnNext(2);
source.OnNext(3);
source.OnNext(4);
source.OnNext(5);
source.OnCompleted();
Console.ReadLine();
}
private static int Process1(int value)
{
if (value == 3)
throw new Exception("error 1");
// do some processing
return value * 2;
}
private static string Process2(int value)
{
if (value == 4)
throw new Exception("error 2");
// do some processing
return value + " good";
}
private static IObservable<Notification<TR>> SelectIfOk<T, TR>(this IObservable<Notification<T>> stream,
Func<T, TR> selector)
{
Func<T, Notification<TR>> trySelector = it =>
{
try
{
var value = selector(it);
return Notification.CreateOnNext(value);
}
catch (Exception ex)
{
return Notification.CreateOnError<TR>(ex);
}
};
return stream.Select(it =>
it.HasValue
? trySelector(it.Value)
: it.Exception != null
? Notification.CreateOnError<TR>(it.Exception)
: Notification.CreateOnCompleted<TR>());
}
如果我想使用中间结果那么,正如我所说的,我将使用元组:
private static Tuple<int, string> Process2(int value)
{
if (value == 4)
throw new Exception("error 2");
// do some processing
return Tuple.Create(value, value * 3 + " good");
}
private static string Process3(Tuple<int, string> value)
{
return value.Item1 + " -> " + value.Item2;
}
(我需要将.SelectIfOk(Process3)
添加到管道中。)
我不愿意将自己的答案标记为正确,所以我会暂时将其打开;但是,据我所知,它确实符合我的要求。