繁体   English   中英

具有select到有状态服务的Observable.Interval会导致奇怪的行为

[英]Observable.Interval with select to stateful service causes strange behaviour

我试图使用Observable.Interval结合select来轮询服务,这似乎是一种语法上很好的方法。 但是每当我尝试实现一种等待observable完成的方法时,我会得到奇怪的行为,其中select多次调用的服务被调用。

没有等待,我得到了正在寻找的正确行为......

private static ConcurrentQueue<string> _data = new ConcurrentQueue<string>(new [] { "a", "b", "c", "d" });

static void Main(string[] args)
{
    var observable = Observable
        .Interval(TimeSpan.FromSeconds(2))
        .Select(Transform)
        .TakeWhile(x => x != null);

    Console.WriteLine("Starting subcription");
    var disposable = observable.Subscribe(x => Console.WriteLine("Event raised for {0}", x));

    Console.WriteLine("Waiting for subcription to complete");
    // need to wait here

    Console.WriteLine("Press any key to exit. . .");
    Console.ReadKey();
}

private static string Transform(long x)
{
    string result;
    _data.TryDequeue(out result);

    Console.WriteLine("Transform invoked [x: {0}, Result: {1}]", x, result ?? "NULL");

    return result;
}

产量

Starting subcription
Waiting for subcription to complete
Press any key to exit. . .
Transform invoked [x: 0, Result: a]
Event raised for a
Transform invoked [x: 1, Result: b]
Event raised for b
Transform invoked [x: 2, Result: c]
Event raised for c
Transform invoked [x: 3, Result: d]
Event raised for d
Transform invoked [x: 4, Result: NULL]

如果我在observable上调用扩展方法wait,它似乎会导致每个间隔调用两次Transform,并且只有一个值返回给事件...

private static ConcurrentQueue<string> _data = new ConcurrentQueue<string>(new [] { "a", "b", "c", "d" });

static void Main(string[] args)
{
    var observable = Observable
        .Interval(TimeSpan.FromSeconds(2))
        .Select(Transform)
        .TakeWhile(x => x != null);

    Console.WriteLine("Starting subcription");
    var disposable = observable.Subscribe(x => Console.WriteLine("Event raised for {0}", x));

    Console.WriteLine("Waiting for subcription to complete");
    observable.Wait();

    Console.WriteLine("Press any key to exit. . .");
    Console.ReadKey();
}

private static string Transform(long x)
{
    string result;
    _data.TryDequeue(out result);

    Console.WriteLine("Transform invoked [x: {0}, Result: {1}]", x, result ?? "NULL");

    return result;
}

产量

Starting subcription
Waiting for subcription to complete
Transform invoked [x: 0, Result: a]
Event raised for a
Transform invoked [x: 0, Result: b]
Transform invoked [x: 1, Result: c]
Event raised for c
Transform invoked [x: 1, Result: d]
Transform invoked [x: 2, Result: NULL]
Transform invoked [x: 2, Result: NULL]
Press any key to exit. . .

我怀疑这是因为Wait在幕后创建了第二个订阅,我的observable后面的服务是有状态的。

我见过人们建议使用ToTask来等待一个observable完成,这有着同样奇怪的行为。

那么,在所有订阅者接收相同数据集的同时,在观察者后面轮询有状态服务的正确方法是什么?

几件事:

  1. 建议仅在Subscribe期间更改状态,而不是Select其他运算符。 我不确定这对你的例子是否实用。
  2. 您的observable是一个冷可观察 ,意味着每个订阅重新创建所有内容:后台每秒钟滴答一个新的计时器,一个新的Select运算符等。您有两个订阅,一个来自Subscribe ,另一个来自Wait ,所以你' ll有两个定时器,两个Select运算符附加调用Transform

您可以通过以下两种方式之一解决此问题:

  1. 将你的观察结果变成一个热的观察者
  2. 消除订阅(@Daniel Weber概述的解决方案)

您作为热点可观测量的可观察量将如下所示:

var observable = Observable
    .Interval(TimeSpan.FromSeconds(2))
    .Select(Transform)
    .TakeWhile(x => x != null)
    .Publish()
    .RefCount();

我的建议是,既然你在你的observable中改变了状态,我会很热,以确保你最终没有运行两次。

确保您只订阅了一次observable。 省略对Subscribe的第一次调用,然后将调用留给Wait 如果您仍想发出一些日志消息(正如您在订阅中所做的那样),请添加一个Do -step:

private static ConcurrentQueue<string> _data = new   ConcurrentQueue<string>(new [] { "a", "b", "c", "d" });

static void Main(string[] args)
{
    var observable = Observable
        .Interval(TimeSpan.FromSeconds(2))
        .Select(Transform)
        .TakeWhile(x => x != null);

    Console.WriteLine("Wait for the observable to complete.");
    observable
        .Do(x => Console.WriteLine("Event raised for {0}", x))
        .Wait();

    Console.WriteLine("Press any key to exit. . .");
    Console.ReadKey();
}

private static string Transform(long x)
{
    string result;
    _data.TryDequeue(out result);

    Console.WriteLine("Transform invoked [x: {0}, Result: {1}]", x, result ?? "NULL");

    return result;
}

请注意, Wait将阻塞(这在Main方法中是不可避免的)。 此外,当您的observable为空时它会抛出。 如果您对observable的任何值不感兴趣,请添加LastOrDefault -step。

等待observable是一种固有的异步操作,所以你应该检查是否可以使用ToTask而不是Wait并在异步方法中等待它。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM