给出了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 => .???.)
答案 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