如何使用yield在并行块或任务中返回Item的集合

时间:2014-12-27 22:11:54

标签: c# task-parallel-library system.reactive

我正在寻求有关如何使用yield关键字在并行块或任务块中返回IEnumberable的帮助。下面是伪代码

public IEnumerable<List<T>> ReadFile( )
{
    foreach (string filepath in lstOfFiles)
    {
        var stream = new FileStream(filepath , FileMode.Open, FileAccess.Read);
        foreach (var item in ReadStream(stream))
            yield return item; //where item is of type List<string>
    }
}

我想将上面的代码转换为并行块,如下所示

lstOfFiles.AsParallel()
          .ForAll(filepath =>
{
    var stream = new FileStream(filepath , FileMode.Open, FileAccess.Read);
    foreach (var item in ReadStream(Stream))
        yield return item;
});

但是编译器抛出了错误,即Yield不能在Parallel块或匿名委托中使用。我也尝试使用Task块,在任务匿名委托中不允许yield

任何人都建议我使用简单和最好的方法来获得并行块或任务中的数据收集。

我读到RX 2.0或TPL在上面的场景中很好用。我怀疑是否利用RX或TPL库来异步返回值的产量。任何人都可以建议我哪个更好Rx或TPL。

如果我使用Rx,是否有必要创建订阅和转换并行块AsObservable。

2 个答案:

答案 0 :(得分:1)

要使用Rx,您必须使用IObservable<T>代替IEnumerable<T>

public IObservable<T> ReadFiles()
{
  return from filepath in lstOfFiles.ToObservable()
         from item in Observable.Using(() => File.OpenRead(filepath), ReadStream)
         select item;
}

每次在Subscribe返回的observable上调用ReadFiles时,它将遍历lstOfFiles中的所有字符串,并行*,读取每个文件流。< / p>

然后,查询打开每个文件流并将其传递给ReadStreamReadFiles负责生成给定流的异步项目序列。

SelectMany查询使用在查询理解语法中编写的ReadStream运算符,将所有ReadStream可观察量生成的每个“项”合并为一个可观察序列,尊重源的异步。

您应该强烈考虑为IEnumerable<T>方法编写async iterator,如我在此处所示;否则,如果你必须返回ToObservable(scheduler),那么你必须通过将public IObservable<Item> ReadStream(Stream stream) { return Observable.Create<Item>(async (observer, cancel) => { // Here's one example of reading a stream with fixed item lengths. var buffer = new byte[itemLength]; // TODO: Define itemLength var remainder = itemLength; int read; do { read = await stream.ReadAsync(buffer, itemLength - remainder, remainder, cancel) .ConfigureAwait(false); remainder -= read; if (read == 0) { if (remainder < itemLength) { throw new InvalidOperationException("End of stream unexpected."); } else { break; } } else if (remainder == 0) { observer.OnNext(ReadItem(buffer)); // TODO: Define ReadItem remainder = itemLength; } } while (true); }); } 运算符应用于引入并发的调度程序来转换它,这可能效率较低。

Subscribe

* Rx在这里不引入任何并发性。并行化只是底层API的异步性质的结果,因此它非常有效。异步读取文件流可能会导致Windows使用I / O完成端口作为优化,在每个缓冲区可用时通知池化线程。这确保Windows完全负责安排应用程序的回调,而不是TPL或您自己。

Rx是自由线程的,因此对观察者的每个通知都可能在不同的池中;但是,由于Rx的序列化合同(§4.2Rx Design Guidelines),当您调用ToList时,您不会在观察者中收到重叠通知,因此不需要提供显式同步,例如锁定。

但是,由于此查询的并行化特性,您可能会观察到有关每个文件的交替通知,但从不重叠通知。

如果您希望一次收到给定文件的所有项目,正如您在问题中所暗示的那样,那么您只需将public IObservable<IList<T>> ReadFiles() { return from filepath in lstOfFiles.ToObservable() from items in Observable.Using(() => File.OpenRead(filepath), ReadStream) .ToList() select items; } 运算符应用于查询并更改返回类型:< / p>

ObserveOnDispatcher

如果您需要观察具有线程关联性的通知(例如,在GUI线程上),那么您必须封送通知,因为它们将到达池化线程。由于此查询本身不引入并发,因此实现此目的的最佳方法是应用ObserveOn(SynchronizationContext)运算符(WPF,Store Apps,Phone,Silverlight)或IEnumerable<T>重载(WinForms,ASP.NET,等等。)。只是不要忘记添加对适当的特定于平台的NuGet包的引用;例如,Rx-Wpf,Rx-WinForms,Rx-WindowsStore等

您可能想要将观察结果转换回Subscribe而不是调用async/await。不要这样做。在大多数情况下,它是不必要的,它可能是低效的,在最坏的情况下,它可能会导致死锁。一旦你进入异步世界,你应该试着留在它。这不仅适用于Rx,也适用于{{1}}。

答案 1 :(得分:0)

您似乎想要使用SelectMany。您不能在匿名方法中使用yield,但可以将其分解为新方法,如下所示:

IEnumerable<Item> items = lstOfFiles.AsParallel()
    .SelectMany(( filepath ) => ReadItems(filepath));

IEnumerable<Item> ReadItems(string filePath)
{
    using(var Stream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
    {
        foreach (var item in ReadStream(Stream))
            yield return item;
    }
}