如何在完成任务后将其变成可观察的元素和过程元素?

时间:2018-07-26 14:29:52

标签: c# system.reactive

给出了Tasks的集合:

var americanAirlines = new FlightPriceChecker("AA");
...
var runningTasks = new List<Task<IList<FlightPrice>>>
{
    americanAirlines.GetPricesAsync(from, to),
    delta.GetPricesAsync(from, to),
    united.GetPricesAsync(from, to)
};

我想以它们到达的顺序处理GetPricesAsync()的结果。目前,我正在使用while循环来实现此目标:

while (runningTasks.Any())
{
    // Wait for any task to finish
    var completed = await Task.WhenAny(runningTasks);
    // Remove from running list   
    runningTasks.Remove(completed);
    // Process the completed task (updates a property we may be binding to)
    UpdateCheapestFlight(completed.Result);
}

使用Rx可以更优雅地解决这个问题吗? 我尝试使用类似下面的代码的方法,但是由于在某个地方我必须await每个getFlightPriceTask会阻塞而只能阻塞然后才执行下一个而不是先做完然后等待下一个:

runningTasks
  .ToObservable()
  .Select(getFlightPriceTask => .???.)

3 个答案:

答案 0 :(得分:3)

尝试一下:

runningTasks
  .Select(getFlightPriceTask => getFlightPriceTask.ToObservable())
  .Merge()
  .Subscribe(flightPrices => UpdateCheapestFlight(flightPrices))

答案 1 :(得分:2)

@Shlomo的回答对我很有帮助(使用Merge()是个窍门!),我想对此发表评论并提出一种替代解决方案。

评论Shlomo的解决方案

此解决方案非常简单,表达了Rx的优雅。唯一的问题是无法等待完成。在生产代码中,这通常不是问题,在生产代码中,我们只关心更新然后绑定到UI的属性。 我的另一条评论是,计算是在Subscribe()中完成的-有些人希望使订阅保持超轻量级,但是我认为这主要是个人喜好。

runningTasks
  // Get all tasks and turn them into Observables.
  .Select(getFlightPriceTask => getFlightPriceTask.ToObservable())
  // Merge all tasks (in my case 3) into one "lane". Think of cars trying
  // to leave a three lane highway and going for a one lane exit.
  .Merge()
  // For every task "leaving the highway" calculate the minimum price.
  .Subscribe(flightPrices => UpdateCheapestFlight(flightPrices))

替代方法1:使用Do()

这根本没有使用Subscribe(),这有点与Rx的想法背道而驰,但是可以等待它,因此其行为类似于原始版本。

await runningTasks
    .Select(getFlightPriceTask => getFlightPriceTask.ToObservable())
    .Merge()
    // Process result of each task.
    .Do(flightPrices => UpdateCheapestFlight(flightPrices))
    // Taking all elements will only complete if all three tasks have completed.
    .Take(runningTasks.Count);

替代2:消除UpdateCheapestFlight()

最后,我认为实现这种Rx样式的一种方法是完全不使用原始的辅助方法,而告诉一个易于阅读的“ Rx故事”。

var minFlightPrice = await runningTasks
    // Get all the tasks and turn them into Observables 
    .Select(getFlightPriceTask => getFlightPriceTask.ToObservable())
    // Merge all three into one "lane".
    .Merge()
    // Get local minimum value of each airline
    .Select(x => x.Min())
    // Take all the local minimums...
    .Take(runningTasks.Count)
    // ...and find the minimum of them.
    .Min();

答案 2 :(得分:0)

这是另一种解决方案:

await runningTasks
    .ToObservable()
    .Merge()
    .Do(result => UpdateCheapestFlight(result))
    .DefaultIfEmpty();

它看起来与Shlomo的solution类似,但有一个细微的区别:任务不是投影到嵌套的可观察对象(IObservable<IObservable<TResult>>),而是投射到任务的可观察对象({{1} })。 Rx包含同时在这两种结构上工作的IObservable<Task<TResult>>运算符的重载。后者效率更高,因为它避免了创建一些一次性的包装器。当我们从异步委托而不是已经实现的任务开始时,前者的功能更强大,因为它允许控制并发级别(通过不立即启动所有任务),并且还可以处理任何未决任务的自动取消如果由于任何原因(包括任何任务中发生的错误)在任何时候都取消了所得的可观察值。

Merge运算符用于按任务完成的顺序处理任务的结果,一次获得一个结果。

最后需要Do运算符,以防止在初始任务列表为空的情况下使用DefaultIfEmpty。这是因为 等待产生的可观察值,并且需要等待可观察值以返回值(最后发出的值)。

下面是上面示例中使用的Rx运算符的签名:

InvalidOperationException