【问题标题】:Observable.Timer or TPL with Task.DelayObservable.Timer 或 TPL 与 Task.Delay
【发布时间】:2015-08-31 19:14:03
【问题描述】:

我有一个要求,在 10 秒的初始延迟后,我需要每 10 分钟执行一次SomeMethod,但需要注意的是,10 分钟计时器应该在SomeMethod 完成后启动。这是粗略的例子:

Start Task 00:00:00
(10 second delay)
SomeMethod executed at 00:00:10 (takes 15 minutes)
(10 minute delay)
SomeMethod executed at 00:25:10 
... and so on.

我知道如何使用 TPL 做到这一点。我可以使用 Task.Delay 启动任务并执行 SomeMethod,然后在每次完成后 (ContinueWith TaskStatus.RanToCompletion),我创建一个新任务并再次执行 SomeMethod

我的问题是这可能使用Observable.Timer 吗?比如……

Observable.Timer(TimeSpan.FromSeconds(10), TimeSpan.FromMinutes(10))

此代码的问题在于,如果 SomeMethod 需要 15 分钟,我将运行两个不同的 SomeMethod 实例,这是我不想要的。我希望 10 分钟计时器在 SomeMethod 完成后启动。这可以使用Observable 还是我应该留在TPL?

编辑:忘了说我希望SomeMethod 在它自己的线程中运行。

【问题讨论】:

    标签: c# task-parallel-library system.reactive


    【解决方案1】:

    您应该对使用Observable.Timer 进行更多调查。它几乎就像您想要的一样直接开箱即用。

    了解 Rx 的一个重要事项是,它保证您永远不会获得单个订阅的并发执行。虽然 Rx 可能会启用各种多线程场景,但它总是会序列化订阅。

    那么,以这个 observable 订阅为例:

    Observable
        .Timer(TimeSpan.FromSeconds(10.0), TimeSpan.FromSeconds(2.0))
        .Timestamp()
        .Subscribe(x =>
        {
            Thread.Sleep(5000);
            Console.WriteLine(x.ToString());
        });
    

    我创建了一个 observable,它将等待 10 秒开始发射值,然后尝试每 2 秒发射一个值。

    然后我添加了.Timestamp() 以准确记录值的生成时间。

    最后,我订阅了一个强制线程休眠 5 秒的观察者。

    这是输出的前 4 个值:

    0@2015-08-31 10:44:34 +00:00
    1@2015-08-31 10:44:39 +00:00
    2@2015-08-31 10:44:44 +00:00
    3@2015-08-31 10:44:49 +00:00
    

    您会注意到这些值之间的间隔为 5 秒。这非常接近你想要的。 Rx 看到两秒过去了,立即执行下一个值。

    但是还有另一个 Rx 运算符可以完全满足您的要求 - .Generate(...)。这是一个非常强大的运算符,可以生成各种可观察的流。

    你想这样使用它:

    Observable
        .Generate(0, x => true, x => x + 1, x => x,
            x => x == 0 ? TimeSpan.FromSeconds(10.0) : TimeSpan.FromSeconds(2.0))
        .Timestamp()
        .Subscribe(x =>
        {
            Thread.Sleep(5000);
            Console.WriteLine(x.ToString());
        });
    

    在这种情况下,它完全按照您想要的方式工作。这是前十个值:

    0@2015-08-31 10:48:27 +00:00
    1@2015-08-31 10:48:34 +00:00
    2@2015-08-31 10:48:41 +00:00
    3@2015-08-31 10:48:48 +00:00
    4@2015-08-31 10:48:55 +00:00
    5@2015-08-31 10:49:02 +00:00
    6@2015-08-31 10:49:09 +00:00
    7@2015-08-31 10:49:16 +00:00
    8@2015-08-31 10:49:23 +00:00
    9@2015-08-31 10:49:30 +00:00
    

    它每 7 秒发射一次。 2 来自 generate 运算符,5 来自观察者。

    你显然可以投入你需要的时间。

    【讨论】:

    • 感谢您的回答。我正在做完全相同的事情,但没有得到结果,因为我错过了问题中的一个重要细节。我想以预期的行为在自己的单独线程中运行 SomeMethod 。我猜它涉及 SubscribeOn(或 ObserveOn?),但我真的很困惑。我对 Rx 有非常基本的了解。
    • @Yogesh - 您可能应该在ObserveOn 中使用EventLoopScheduler 的新实例,或者直接在.Generate(...) 中添加。
    • 您的Generate 解决方案与SubscribeOn 完美配合(Timer 不行)。我还阅读了使用TaskPoolScheduler.DefaultLongRunningTask 问题,但它似乎对ObserveOn 的影响更大,因为它试图为每个观察创建一个线程。尽管我仍在分析,但我认为该解决方案应该可以在没有任何 OOM 异常的情况下工作。如果它似乎确实造成了与内存相关的问题,我仍然有TaskPoolScheduler.Default.DisableOptimizations(typeof(ISchedulerLongRunning))。 :)
    【解决方案2】:

    假设SomeMethod在完成时发出一个OnCompleted事件,我们可以将它写成Observable

    //If SomeMethod OnCompleted conforms to .NET Event Pattern
    var completedObservable = Observable.FromEventPattern<OnCompletedEventArgs>(
                e => this.OnCompleted += e,
                e => this.OnCompleted += e);
    
    //Subscribe to OnCompleted events
    var repeatDisposable = completedObservable.Subscribe(_ => 
                                    Observable.Timer(TimeSpan.FromMinutes(10))
                                              .Subscribe(_ => SomeMethod()));
    
    //Start condition
    var starterDisposable = Observable.Timer(TimeSpan.FromSeconds(10))
                                      .Subscribe(_ => SomeMethod());
    

    【讨论】:

    • 我的意思是ContinueWith TaskStatus.RanToCompletion,而不是ContinueWith OnCompleted。修复有问题。该方法不实现任何完成事件。不过,这是一个不错的解决方案。赞成。
    • 我不确定使用 Completed 事件开始污染方法是否是个好主意。我也会对在订阅中使用订阅保持警惕,部分原因是与一次性相关的问题,部分原因是您的订阅应该在那里处理蒸汽的输出,而不是启动新的输出。
    • 感谢@Chris 的评论,您对如何改进订阅有什么建议吗?
    • 一般来说,如果你想将 observables 串在一起,那么使用 SelectMany 创建 1 个管道是最好的方法。然后,您可以拥有一个使用此管道的订阅块。这在评论中看起来不会很漂亮,而是像这样:(从 Obs1() 中的 result1 从 Obs2() 中的 result2 中选择 result2).Subscribe(x => {...}, ex => {.. .})
    猜你喜欢
    • 2015-01-04
    • 1970-01-01
    • 2013-09-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-15
    • 2014-06-21
    相关资源
    最近更新 更多