csa*_*uve 4 c# system.reactive async-await corecursion
我正在尝试创建一个Observable,其中每个项目都是通过异步任务生成的.下一项应该通过对前一项结果的异步调用(共同递归)生成.在"生成"这个用语中,这看起来像这样 - 除了Generate不支持异步(它也不支持初始状态的委托).
var ob = Observable.Generate(
async () => await ProduceFirst(), // Task<T> ProduceFirst()
prev => Continue(prev) // bool Continue(T);
async prev => await ProduceNext(prev) // Task<T> ProduceNext(T)
item => item
);
Run Code Online (Sandbox Code Playgroud)
作为一个更具体的示例,要通过一次获取100条消息来查看ServiceBus队列中的所有消息,请按如下方式实现ProduceFirst,Continue和ProduceNext:
Task<IEnumerable<BrokeredMessage>> ProduceFirst()
{
const int batchSize = 100;
return _serviceBusReceiver.PeekBatchAsync(batchSize);
}
bool Continue(IEnumerable<BrokeredMessage> prev)
{
return prev.Any();
}
async Task<IEnumerable<BrokeredMessage>> ProduceNext(IEnumerable<BrokeredMessage> prev)
{
const int batchSize = 100;
return (await _serviceBusReceiver.PeekBatchAsync(prev.Last().SequenceNumber, batchSize + 1)).Skip(1)
}
Run Code Online (Sandbox Code Playgroud)
然后调用.SelectMany(i => i)上IObservable<IEnumerable<BrokeredMessage>>把它变成一个IObservable<BrokeredMessage>
其中_serviceBusReceiver是接口的实例,如下所示:
public interface IServiceBusReceiver
{
Task<IEnumerable<BrokeredMessage>> PeekBatchAsync(int batchSize);
Task<IEnumerable<BrokeredMessage>> PeekBatchAsync(long fromSequenceNumber, int batchSize);
}
Run Code Online (Sandbox Code Playgroud)
BrokeredMessage来自https://msdn.microsoft.com/en-us/library/microsoft.servicebus.messaging.brokeredmessage.aspx
pau*_*els 11
如果您打算使用自己的异步Generate函数,我建议使用递归调度而不是包装while循环.
public static IObservable<TResult> Generate<TResult>(
Func<Task<TResult>> initialState,
Func<TResult, bool> condition,
Func<TResult, Task<TResult>> iterate,
Func<TResult, TResult> resultSelector,
IScheduler scheduler = null)
{
var s = scheduler ?? Scheduler.Default;
return Observable.Create<TResult>(async obs => {
return s.Schedule(await initialState(), async (state, self) =>
{
if (!condition(state))
{
obs.OnCompleted();
return;
}
obs.OnNext(resultSelector(state));
self(await iterate(state));
});
});
}
Run Code Online (Sandbox Code Playgroud)
这有几个优点.首先,你可以取消这个,只需一个简单的while循环就无法直接取消它,事实上你甚至不会在observable完成之前返回subscribe函数.其次,这可以让你控制每个项目的调度/异步(这使得测试变得轻而易举),这也使它更适合于库
经过大量测试后,我认为使用内置的 Rx 运算符可以很好地完成这项工作。
public static IObservable<TResult> Generate<TResult>(
Func<Task<TResult>> initialState,
Func<TResult, bool> condition,
Func<TResult, Task<TResult>> iterate,
Func<TResult, TResult> resultSelector,
IScheduler scheduler = null)
{
return Observable.Create<TResult>(o =>
{
var current = default(TResult);
return
Observable
.FromAsync(initialState)
.Select(y => resultSelector(y))
.Do(c => current = c)
.Select(x =>
Observable
.While(
() => condition(current),
Observable
.FromAsync(() => iterate(current))
.Select(y => resultSelector(y))
.Do(c => current = c))
.StartWith(x))
.Switch()
.Where(x => condition(x))
.ObserveOn(scheduler ?? Scheduler.Default)
.Subscribe(o);
});
}
Run Code Online (Sandbox Code Playgroud)
我已经使用以下内容测试了此代码:
bool Continue(IEnumerable<BrokeredMessage> prev)
{
return prev.Any();
}
Task<IEnumerable<BrokeredMessage>> ProduceFirst()
{
return
Task.FromResult(
EnumerableEx.Return(
new BrokeredMessage()
{
SequenceNumber = 1
}));
}
Task<IEnumerable<BrokeredMessage>> ProduceNext(IEnumerable<BrokeredMessage> prev)
{
return Task.FromResult(
prev.Last().SequenceNumber < 3
? EnumerableEx.Return(
new BrokeredMessage()
{
SequenceNumber = prev.Last().SequenceNumber + 1
})
: Enumerable.Empty<BrokeredMessage>());
}
public class BrokeredMessage
{
public int SequenceNumber;
}
Run Code Online (Sandbox Code Playgroud)
并运行此序列:
var ob = Generate(
async () => await ProduceFirst(),
prev => Continue(prev),
async prev => await ProduceNext(prev),
item => item);
Run Code Online (Sandbox Code Playgroud)
我得到了这个结果:

我的测试代码还使用了 Reactive Extension 团队的 Interactive Extensions - NuGet "Ix-Main"。
| 归档时间: |
|
| 查看次数: |
1070 次 |
| 最近记录: |