Udp 顺序反应性扩展事件
我在多个线程中通过UDP接收消息。每次接收后,我都会发出Udp 顺序反应性扩展事件,udp,system.reactive,reactive-programming,Udp,System.reactive,Reactive Programming,我在多个线程中通过UDP接收消息。每次接收后,我都会发出MessageReceived.OnNext(message) 因为我使用了多个线程,所以消息引发的无序是一个问题 如何通过消息计数器命令消息的提升? (假设存在message.counter属性) 必须记住,一条消息可能会在通信中丢失(假设在X条消息之后有一个计数器孔,该孔未填满,我将发出下一条消息) 必须尽快发出消息(如果收到下一个计数器)在说明检测丢失消息的要求时,您没有考虑最后一条消息未到达的可能性;我已经添加了一个时间超时,它刷新
MessageReceived.OnNext(message)
因为我使用了多个线程,所以消息引发的无序是一个问题
如何通过消息计数器命令消息的提升?
(假设存在message.counter属性)
必须记住,一条消息可能会在通信中丢失(假设在X条消息之后有一个计数器孔,该孔未填满,我将发出下一条消息)
必须尽快发出消息(如果收到下一个计数器)在说明检测丢失消息的要求时,您没有考虑最后一条消息未到达的可能性;我已经添加了一个<代码>时间超时,它刷新缓冲消息,如果在给定的时间内什么都没有到达,你可能会想考虑这个错误,看看如何做这件事的评论。 我将通过定义具有以下签名的扩展方法来解决此问题:
public static IObservable<TSource> Sort<TSource>(
this IObservable<TSource> source,
Func<TSource, int> keySelector,
TimeSpan timeoutDuration = new TimeSpan(),
int gapTolerance = 0)
安排超时
现在,如果请求超时,我们将用一个副本替换源,如果消息没有在timeoutDuration
中到达,该副本将简单地终止并发送OnCompleted
if(timeoutDuration != TimeSpan.Zero)
source = source.Timeout(
timeoutDuration,
Observable.Empty<TSource>(),
scheduler);
初始化一些变量
我们将在nextKey
中跟踪下一个预期的键值,并创建一个SortedDictionary
来保存无序消息,直到它们可以发送
int nextKey = 0;
var buffer = new SortedDictionary<int, TSource>();
我们调用keySelector
函数从消息中提取密钥:
var key = keySelector(x);
if(key == nextKey)
{
nextKey++;
o.OnNext(x);
}
如果消息有一个旧密钥(因为它超出了我们对无序消息的容忍度),我们将删除它并处理此消息(您可能希望采取不同的操作):
或者,我们可能有一个无序的未来消息,在这种情况下,我们必须将它添加到我们的缓冲区。如果我们这样做,我们还必须确保我们的缓冲区没有超出存储无序消息的容差-在这种情况下,我们还会将nextKey
跳到缓冲区中的第一个键,因为它是SortedDictionary
是下一个最低的键:
else if(key > nextKey)
{
buffer.Add(key, x);
if(gapTolerance != 0 && buffer.Count > gapTolerance)
nextKey = buffer.First().Key;
}
现在,不管上面的结果如何,我们都需要清空缓冲区中现在准备就绪的所有键。为此,我们使用一个助手方法。请注意,它会调整nextKey
,因此我们必须小心地通过引用传递它。我们只需在缓冲区上循环读取、删除和发送消息,只要键彼此连续,每次递增nextKey
:
private static void SendNextConsecutiveKeys<TSource>(
ref int nextKey,
IObserver<TSource> observer,
SortedDictionary<int, TSource> buffer)
{
TSource x;
while(buffer.TryGetValue(nextKey, out x))
{
buffer.Remove(nextKey);
nextKey++;
observer.OnNext(x);
}
}
全面实施
下面是完整的实现
public static IObservable<TSource> Sort<TSource>(
this IObservable<TSource> source,
Func<TSource, int> keySelector,
int gapTolerance = 0,
TimeSpan timeoutDuration = new TimeSpan(),
IScheduler scheduler = null)
{
scheduler = scheduler ?? Scheduler.Default;
if(timeoutDuration != TimeSpan.Zero)
source = source.Timeout(
timeoutDuration,
Observable.Empty<TSource>(),
scheduler);
return Observable.Create<TSource>(o => {
int nextKey = 0;
var buffer = new SortedDictionary<int, TSource>();
return source.Subscribe(x => {
var key = keySelector(x);
// drop stale keys
if(key < nextKey) return;
if(key == nextKey)
{
nextKey++;
o.OnNext(x);
}
else if(key > nextKey)
{
buffer.Add(key, x);
if(gapTolerance != 0 && buffer.Count > gapTolerance)
nextKey = buffer.First().Key;
}
SendNextConsecutiveKeys(ref nextKey, o, buffer);
},
o.OnError,
() => {
// empty buffer on completion
foreach(var item in buffer)
o.OnNext(item.Value);
o.OnCompleted();
});
});
}
private static void SendNextConsecutiveKeys<TSource>(
ref int nextKey,
IObserver<TSource> observer,
SortedDictionary<int, TSource> buffer)
{
TSource x;
while(buffer.TryGetValue(nextKey, out x))
{
buffer.Remove(nextKey);
nextKey++;
observer.OnNext(x);
}
}
闭幕词
这里有各种有趣的替代方法。我之所以选择这种基本上是强制性的方法,是因为我认为这是最容易遵循的——但可能有一些花哨的分组把戏可以用来实现这一点。关于Rx,我知道有一件事是一贯正确的——给猫剥皮的方法总是很多的
我对这里的超时概念也不太满意——在生产系统中,我想实现一些检查连接的方法,比如心跳或类似的方法。我没有涉及到这一点,因为它显然是特定于应用程序的。此外,心跳已经在这些委员会和其他地方讨论过了()), < P>强烈地考虑使用TCP,如果你想要可靠的排序——那就是它的目的;否则,您将被迫使用UDP玩猜测游戏,有时您会出错 例如,假设您按以下顺序接收以下数据报:[A、B、D] 当您收到D时,在按下D之前,您应该等待C到达多长时间 无论你选择什么时间,你都可能是错的:
MessageReceived.OnNext
表示您正在使用主题,这可能是不必要的。考虑将ASYNC代码> UDPcli客< /Cult>方法直接转换成可观测值,或者通过编写一个异步迭代器来转换它们,通过<代码>可观察到.CREATE(ASYNC(观察者,取消)= {{}})
我曾经考虑过创建一个Rxx的OrderBy
操作符的OrderByUntil
变体,它本可以用类似的方式解决这个问题,但最终我觉得很奇怪。你认为这是值得实施的,还是仅仅是一个解决一个糟糕问题的好办法?请看我对你答案的评论!主要是后者:)我完全同意Dave关于TCP的观点——但排序问题很有趣,它偶尔也会有有效的应用程序——我在一个自定义Tibco消息传递场景中遇到了它,我们从多台机器收集消息+1 DaveWell,我绝对同意这很有趣,特别是因为Rx没有提供任何真正直接的方法来解决这个问题。尽管我认为类似于以下的东西可以工作:datagrams.Buffer(time).Scan(…).Select(…)
尽管最后一个查询意味着所有通知都会延迟。看起来,Observable.Create
实际上更简单。另外,不要忘了WCF的许多功能,它们在这方面很有帮助。例如
if(key == nextKey)
{
nextKey++;
o.OnNext(x);
}
else if(key > nextKey)
{
buffer.Add(key, x);
if(gapTolerance != 0 && buffer.Count > gapTolerance)
nextKey = buffer.First().Key;
}
private static void SendNextConsecutiveKeys<TSource>(
ref int nextKey,
IObserver<TSource> observer,
SortedDictionary<int, TSource> buffer)
{
TSource x;
while(buffer.TryGetValue(nextKey, out x))
{
buffer.Remove(nextKey);
nextKey++;
observer.OnNext(x);
}
}
() => {
// empty buffer on completion
foreach(var item in buffer)
o.OnNext(item.Value);
o.OnCompleted();
});
public static IObservable<TSource> Sort<TSource>(
this IObservable<TSource> source,
Func<TSource, int> keySelector,
int gapTolerance = 0,
TimeSpan timeoutDuration = new TimeSpan(),
IScheduler scheduler = null)
{
scheduler = scheduler ?? Scheduler.Default;
if(timeoutDuration != TimeSpan.Zero)
source = source.Timeout(
timeoutDuration,
Observable.Empty<TSource>(),
scheduler);
return Observable.Create<TSource>(o => {
int nextKey = 0;
var buffer = new SortedDictionary<int, TSource>();
return source.Subscribe(x => {
var key = keySelector(x);
// drop stale keys
if(key < nextKey) return;
if(key == nextKey)
{
nextKey++;
o.OnNext(x);
}
else if(key > nextKey)
{
buffer.Add(key, x);
if(gapTolerance != 0 && buffer.Count > gapTolerance)
nextKey = buffer.First().Key;
}
SendNextConsecutiveKeys(ref nextKey, o, buffer);
},
o.OnError,
() => {
// empty buffer on completion
foreach(var item in buffer)
o.OnNext(item.Value);
o.OnCompleted();
});
});
}
private static void SendNextConsecutiveKeys<TSource>(
ref int nextKey,
IObserver<TSource> observer,
SortedDictionary<int, TSource> buffer)
{
TSource x;
while(buffer.TryGetValue(nextKey, out x))
{
buffer.Remove(nextKey);
nextKey++;
observer.OnNext(x);
}
}
public static void Main()
{
var tests = new Tests();
tests.Test();
}
public class Tests : ReactiveTest
{
public void Test()
{
var scheduler = new TestScheduler();
var xs = scheduler.CreateColdObservable(
OnNext(100, 0),
OnNext(200, 2),
OnNext(300, 1),
OnNext(400, 4),
OnNext(500, 5),
OnNext(600, 3),
OnNext(700, 7),
OnNext(800, 8),
OnNext(900, 9),
OnNext(1000, 6),
OnNext(1100, 12),
OnCompleted(1200, 0));
//var results = scheduler.CreateObserver<int>();
xs.Sort(
keySelector: x => x,
gapTolerance: 2,
timeoutDuration: TimeSpan.FromTicks(200),
scheduler: scheduler).Subscribe(Console.WriteLine);
scheduler.Start();
}
}