在C#中使用Rx我正在尝试为REST API创建轮询请求。我面临的问题是,Observable需要按顺序发送回复。意味着如果请求A在X时间进行,并且请求B在X + dx时间进行,并且B的响应在A之前出现,则Observable表达式应该忽略或取消请求A.
我编写了一个示例代码,试图描述该场景。我如何修复它以获取最新的响应并取消或忽略之前的响应。
class Program
{
static int i = 0;
static void Main(string[] args)
{
GenerateObservableSequence();
Console.ReadLine();
}
private static void GenerateObservableSequence()
{
var timerData = Observable.Timer(TimeSpan.Zero,
TimeSpan.FromSeconds(1));
var asyncCall = Observable.FromAsync<int>(() =>
{
TaskCompletionSource<int> t = new TaskCompletionSource<int>();
i++;
int k = i;
var rndNo = new Random().Next(3, 10);
Task.Delay(TimeSpan.FromSeconds(rndNo)).ContinueWith(r => { t.SetResult(k); });
return t.Task;
});
var obs = from t in timerData
from data in asyncCall
select data;
var hot = obs.Publish();
hot.Connect();
hot.Subscribe(j =>
{
Console.WriteLine("{0}", j);
});
}
}
@Enigmativity回答:添加轮询异步功能以始终采取最新响应:
public static IObservable<T> PollingAync<T> (Func<Task<T>> AsyncCall, double TimerDuration)
{
return Observable
.Create<T>(o =>
{
var z = 0L;
return
Observable
.Timer(TimeSpan.Zero, TimeSpan.FromSeconds(TimerDuration))
.SelectMany(nr =>
Observable.FromAsync<T>(AsyncCall),
(nr, obj) => new { nr, obj})
.Do(res => z = Math.Max(z, res.nr))
.Where(res => res.nr >= z)
.Select(res => res.obj)
.Subscribe(o);
});
}
答案 0 :(得分:5)
这是一种常见的情况,可以简单修复。
相关示例代码的关键部分是
var obs = from t in timerData
from data in asyncCall
select data;
这可以理解为&#34; timerData
中的每个值都会获得asyncCall
&#34;中的所有值。这是SelectMany
(或FlatMap
)运算符。 SelectMany
运算符将从内部序列(asyncCall
)中获取所有值,并在收到它们时返回它们的值。这意味着您可以获得无序值。
当外部序列(timerData
)产生新值时,您想要取消先前的内部序列。为此,我们希望使用Switch
运算符。
var obs = timerData.Select(_=>asyncCall)
.Switch();
可以将完整代码清除到以下内容。 (删除多余的发布/连接,按键处理订阅)
课程计划 { static int i = 0;
static void Main(string[] args)
{
using (GenerateObservableSequence().Subscribe(x => Console.WriteLine(x)))
{
Console.ReadLine();
}
}
private static IObservable<int> GenerateObservableSequence()
{
var timerData = Observable.Timer(TimeSpan.Zero,
TimeSpan.FromSeconds(1));
var asyncCall = Observable.FromAsync<int>(() =>
{
TaskCompletionSource<int> t = new TaskCompletionSource<int>();
i++;
int k = i;
var rndNo = new Random().Next(3, 10);
Task.Delay(TimeSpan.FromSeconds(rndNo)).ContinueWith(r => { t.SetResult(k); });
return t.Task;
});
return from t in timerData
from data in asyncCall
select data;
}
}
- 编辑 -
看起来我误解了这个问题。 @Enigmativity提供了更准确的答案。这是他的回答的清理。
//Probably should be a field?
var rnd = new Random();
var obs = Observable.Timer(TimeSpan.Zero, TimeSpan.FromSeconds(1))
//.Select(n => new { n, r = ++i })
//No need for the `i` counter. Rx does this for us with this overload of `Select`
.Select((val, idx) => new { Value = val, Index = idx})
.SelectMany(nr =>
Observable.Timer(TimeSpan.FromSeconds(rnd.Next(3, 10))),
(nr, _) => nr)
//.Do(nr => z = Math.Max(z, nr.n))
//.Where(nr => nr.n >= z)
//Replace external State and Do with scan and Distinct
.Scan(new { Value = 0L, Index = -1 }, (prev, cur) => {
return cur.Index > prev.Index
? cur
: prev;
})
.DistinctUntilChanged()
.Select(nr => nr.Value)
.Dump();
答案 1 :(得分:2)
让我们从简化代码开始。
这基本上是相同的代码:
var rnd = new Random();
var i = 0;
var obs =
from n in Observable.Timer(TimeSpan.Zero, TimeSpan.FromSeconds(1))
let r = ++i
from t in Observable.Timer(TimeSpan.FromSeconds(rnd.Next(3, 10)))
select r;
obs.Subscribe(Console.WriteLine);
我得到了这样的结果:
2 1 3 4 8 5 11 6 9 7 10
或者,这可以写成:
var obs =
Observable
.Timer(TimeSpan.Zero, TimeSpan.FromSeconds(1))
.Select(n => ++i)
.SelectMany(n =>
Observable.Timer(TimeSpan.FromSeconds(rnd.Next(3, 10))), (n, _) => n);
所以,现在满足您的要求:
如果请求A在X时间进行,请求B在X + dx时间进行,B的响应在A之前出现,则Observable表达式应该忽略或取消请求A.
以下是代码:
var rnd = new Random();
var i = 0;
var z = 0L;
var obs =
Observable
.Timer(TimeSpan.Zero, TimeSpan.FromSeconds(1))
.Select(n => new { n, r = ++i })
.SelectMany(nr =>
Observable.Timer(TimeSpan.FromSeconds(rnd.Next(3, 10))), (nr, _) => nr)
.Do(nr => z = Math.Max(z, nr.n))
.Where(nr => nr.n >= z)
.Select(nr => nr.r);
我不喜欢像这样使用.Do
,但我还不能想到另一种选择。
这就是这样的事情:
1 5 8 9 10 11 14 15 16 17 22
请注意,这些值只是升序。
现在,您真的应该使用Observable.Create
来封装您正在使用的状态。所以你的最终观察结果应该是这样的:
var obs =
Observable
.Create<int>(o =>
{
var rnd = new Random();
var i = 0;
var z = 0L;
return
Observable
.Timer(TimeSpan.Zero, TimeSpan.FromSeconds(1))
.Select(n => new { n, r = ++i })
.SelectMany(nr =>
Observable.Timer(TimeSpan.FromSeconds(rnd.Next(3, 10))),
(nr, _) => nr)
.Do(nr => z = Math.Max(z, nr.n))
.Where(nr => nr.n >= z)
.Select(nr => nr.r)
.Subscribe(o);
});