将IEnumerable <t>转换为IObservable <t>,具有最大并行度</t> </t>

时间:2014-08-21 21:50:07

标签: c# async-await system.reactive

我有一系列异步任务要做(比如,获取N个网页)。现在我想要的是将它们全部公开为IObservable<T>。我目前的解决方案使用this question的答案:

async Task<ResultObj> GetPage(string page) {
    Console.WriteLine("Before");
    var result = await FetchFromInternet(page);
    Console.WriteLine("After");
    return result;
}

// pages is an IEnumerable<string>
IObservable<ResultObj> resultObservable =pages.Select(GetPage).
                 Select(t => Observable.FromAsync(() => t)).Merge();

// Now consume the list
foreach(ResultObj obj in resultObservable.ToEnumerable()) {
    Console.WriteLine(obj.ToString());
}

问题在于我不知道要获取的页面数量,而且可能很大。我不想同时发出数百个请求。所以我想要一种方法来限制并行执行的最大任务数。有没有办法限制GetPage的并发调用次数?

有一个Merge重载需要一个maxConcurrent参数,但它似乎并没有实际限制函数invokation的并发性。控制台在After消息之前打印所有Before消息。

注意:我需要转换回IEnumerable<T>。我正在编写一个系统的数据源,它给我提取数据的描述符,我需要给它一个下载数据的列表。

1 个答案:

答案 0 :(得分:7)

修改

以下内容应该有效。 This overload限制了并发订阅的数量。

var resultObservable = pages
  .Select(p => Observable.FromAsync(() => GetPage(p)))
  .Merge(maxConcurrent);

说明

为了理解为什么需要这种改变,我们需要一些背景知识

  1. FromAsync返回一个observable,它将调用传递的Func every time it is subscribed to。这意味着如果observable从未订阅,则永远不会调用它。

  2. Merge热切地读取源序列,并且只有同时订阅n个可观测量。

  3. 通过这两个部分,我们可以知道为什么原始版本将并行执行所有内容:由于(2),GetPage将在Merge确定的时间内为所有源字符串调用GetPage需要订阅多少个观察者。

    我们也可以看到为什么第二个版本有效:即使序列已完全迭代,(1)意味着在Merge决定需要订阅n之前不会调用n {1}}可观察者。这导致仅同时执行{{1}}任务的期望结果。