【问题标题】:Why is ParallelQuery<T>.Where not working when converting to Observable?为什么转换为 Observable 时 ParallelQuery<T>.Where 不起作用?
【发布时间】:2010-02-18 10:51:48
【问题描述】:

我有一个要并行处理的可观察集合,然后在过滤时观察处理后的值,最后订阅一个接收过滤值的处理程序。

我的示例在语法上是正确的并且编译得很好,当我运行代码时,会评估执行过滤的 Where 语句。但是没有数据通过订阅。如果我删除 AsParallel 以便通过常规 IEnumerable 完成处理,则数据会通过并且一切都按预期工作。

这是我的示例,对字符串进行一些处理:

// Generate some data every second
var strings = Observable.Generate(() =>
    new TimeInterval<Notification<string>>(
        new Notification<string>
            .OnNext(DateTime.Now.ToString()), TimeSpan.FromSeconds(1)));

// Process the data in parallel
var parallelStrings = from value in strings.ToEnumerable().AsParallel()
                      select "Parallel " + value;

// Filter and observe
var data = String.Empty;
parallelStrings
    .Where(value => !String.IsNullOrEmpty(value))
    .ToObservable()
    .Subscribe(value => data = value);

下一个奇怪的事情是,如果我使用 TakeWhile 运算符,在我看来,它在概念上类似于 Where,观察 ParallelQuery 会按预期工作:

// Filter and observe
var data = String.Empty;
parallelStrings
    .TakeWhile(cs => !String.IsNullOrEmpty(cs))
    .ToObservable()
    .Subscribe(value => data = value);

向订阅中添加一些日志记录代码表明在ToObservable 转换之前接收到数据,但不是在转换之后:

1.    var data = String.Empty;
2.    parallelStrings
3.        .Where(value => !String.IsNullOrEmpty(value))
4.        .Select(value => value)
5.        .ToObservable()
6.        .Select(value => value)
7.        .Subscribe(value => data = value);

第 4 行 lambda 中的断点被命中,而第 6 行 lambda 中的断点从未被命中。

为什么TakeWhile 会向订阅者发送数据,而Where 不会?

如果它很重要,我会在 Visual Studio 2010 RC 中使用面向 .Net 4.0 Framework Client Profile 的项目开发我的代码。

更新:基于@Sergeys answer,我重新设计了Where 过滤器的位置。以下代码按预期工作:

var processedStrings = from value in strings
                       let processedValue = "Parallel " + value
                       where !String.IsNullOrEmpty(processedValue)
                       select processedValue;

var data = String.Empty;
processedStrings
    .ToEnumerable()
    .AsParallel()
    .ToObservable()
    .Subscribe(value => data = value );

仍然感觉有点尴尬,必须先将初始 observable processedStrings 转换为 enumerable 以使其并行化,然后再将其转换回 observable 以订阅最终结果。

【问题讨论】:

  • 另一个修复可能是在 .ToObservable 之前使用 .AsOrdered - 没有尝试过。
  • .AsParallel().Where(...).AsOrdered().ToObservable()... 失败:System.InvalidOperationException: AsOrdered may only be called on the result of AsParallel, ParallelEnumerable.Range, or ParallelEnumerable.Repeat. 似乎是为了过滤 after并行化,唯一的选择是使用 .TakeWhile。

标签: c# system.reactive parallel-extensions


【解决方案1】:

来自C# 4.0 in a Nutshell


目前 PLINQ 可以并行化的内容存在一些实际限制。这些 后续服务包和框架版本可能会放松限制。 以下查询运算符会阻止查询被并行化,除非 源元素位于其原始索引位置:

  • Take、TakeWhile、Skip 和 SkipWhile
  • Select、SelectMany 和 ElementAt 的索引版本

大多数查询运算符会更改元素的索引位置(包括那些 删除元素,例如 Where)。这意味着如果你想使用前面的 运算符,它们通常需要位于查询的开头


所以,事实上,使用 TakeWhile 可以防止 .AsParallel() 并行化。很难说为什么在哪里杀死了订阅,但把它放在 AsParallel 之前可能解决问题。

【讨论】:

  • 你的意思是 .TakeWhile 使用 before .AsParallel 会阻止并行化吗?我有点困惑这如何解释我所看到的。在我的示例中,据我所知,TakeWhile 和 Where 都不会破坏并行化部分(数据总是到达 Where lambda)。另一方面, .Where 以某种方式阻止了数据被 observable 进一步推送。
  • 不,相反,使用 beforeAsParallel 的 TakeWhile 不会阻止并行化。罪魁祸首是 AsObservable 由于某种原因(我不知道为什么)不适用于进入管道的并行数据。
【解决方案2】:

TakeWhile 在概念上并不等同于Where,因为它取决于排序。我怀疑查询实际上是按顺序执行的(请参阅this blog post)。尝试在您的 TakeWhile 示例中调用 .WithExecutionMode(ParallelExecutionMode.ForceParallelism),我怀疑您会看到相同的结果。

我不知道为什么它在并行情况下不起作用...我可以建议您进行一些日志记录以查看数据到达多远?例如,您可以使用 Select 执行有用的日志记录,它在记录后返回原始项目。

【讨论】:

  • 嗯,好吧,如果它是按顺序执行的,也许它已经工作了。正是引入了 AsParallel() 来停止处理。添加 WithExecutionMode 不会改变行为:使用 Where 过滤时数据仍然无法通过。我将尝试添加更多日志记录,但 Where lambda 中的断点显示数据至少到达 Where 运算符...
  • @Peter:反对票是几乎同时发生的一系列反对票之一,所以可能只是一个怀恨在心的人。 WithExecutionMode(...) 的想法是显示它也与 TakeWhile 一起失败,而不是让 Where 工作。您可能希望在 ToObservable() 之前放置一个日志记录操作,以查看 observable 实际接收到的内容。此外,可能值得研究是否有某种方法可以并行化 observable 本身——像这样在两个模型之间来回穿梭感觉很奇怪。
  • 添加了仅确认事实的日志记录:直到最终转换为 observable 才收到数据。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-01
  • 2021-12-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-14
相关资源
最近更新 更多