在冷IObservable上暂停和恢复订阅

时间:2011-10-01 12:22:58

标签: c# multithreading asynchronous system.reactive

使用Rx,我希望在以下代码中暂停和恢复功能:

如何实现Pause()和Resume()?

    static IDisposable _subscription;

    static void Main(string[] args)
    {
        Subscribe();
        Thread.Sleep(500);
        // Second value should not be shown after two seconds:
        Pause();
        Thread.Sleep(5000);
        // Continue and show second value and beyond now:
        Resume();
    }

    static void Subscribe()
    {
        var list = new List<int> { 1, 2, 3, 4, 5 };
        var obs = list.ToObservable();
        _subscription = obs.SubscribeOn(Scheduler.NewThread).Subscribe(p =>
        {
            Console.WriteLine(p.ToString());
            Thread.Sleep(2000);
        },
        err => Console.WriteLine("Error"),
        () => Console.WriteLine("Sequence Completed")
        );
    }

    static void Pause()
    {
        // Pseudocode:
        //_subscription.Pause();
    }

    static void Resume()
    {
        // Pseudocode:
        //_subscription.Resume();
    }

Rx解决方案?

  • 我相信我可以使用某种布尔字段选通与线程锁定相结合(Monitor.WaitMonitor.Pulse

  • 但是有没有Rx操作符或其他一些反应速记来实现同样的目标?

4 个答案:

答案 0 :(得分:12)

这是一种相当简单的Rx方式来做你想要的。我创建了一个名为Pausable的扩展方法,它接受一个源可观察对象和一个暂停或恢复可观察对象的布尔值的第二个可观察对象。

public static IObservable<T> Pausable<T>(
    this IObservable<T> source,
    IObservable<bool> pauser)
{
    return Observable.Create<T>(o =>
    {
        var paused = new SerialDisposable();
        var subscription = Observable.Publish(source, ps =>
        {
            var values = new ReplaySubject<T>();
            Func<bool, IObservable<T>> switcher = b =>
            {
                if (b)
                {
                    values.Dispose();
                    values = new ReplaySubject<T>();
                    paused.Disposable = ps.Subscribe(values);
                    return Observable.Empty<T>();
                }
                else
                {
                    return values.Concat(ps);
                }
            };

            return pauser.StartWith(false).DistinctUntilChanged()
                .Select(p => switcher(p))
                .Switch();
        }).Subscribe(o);
        return new CompositeDisposable(subscription, paused);
    });
}

可以像这样使用:

var xs = Observable.Generate(
    0,
    x => x < 100,
    x => x + 1,
    x => x,
    x => TimeSpan.FromSeconds(0.1));

var bs = new Subject<bool>();

var pxs = xs.Pausable(bs);

pxs.Subscribe(x => { /* Do stuff */ });

Thread.Sleep(500);
bs.OnNext(true);
Thread.Sleep(5000);
bs.OnNext(false);
Thread.Sleep(500);
bs.OnNext(true);
Thread.Sleep(5000);
bs.OnNext(false);

使用Pause&amp;将其放入代码中应该相当容易。 Resume方法。

答案 1 :(得分:3)

这里作为IConnectableObservable的应用程序,我稍微纠正了较新的api(原始here):

public static class ObservableHelper {
    public static IConnectableObservable<TSource> WhileResumable<TSource>(Func<bool> condition, IObservable<TSource> source) {
        var buffer = new Queue<TSource>();
        var subscriptionsCount = 0;
        var isRunning = System.Reactive.Disposables.Disposable.Create(() => {
            lock (buffer)
            {
                subscriptionsCount--;
            }
        });
        var raw = Observable.Create<TSource>(subscriber => {
            lock (buffer)
            {
                subscriptionsCount++;
                if (subscriptionsCount == 1)
                {
                    while (buffer.Count > 0) {
                        subscriber.OnNext(buffer.Dequeue());
                    }
                    Observable.While(() => subscriptionsCount > 0 && condition(), source)
                        .Subscribe(
                            v => { if (subscriptionsCount == 0) buffer.Enqueue(v); else subscriber.OnNext(v); },
                            e => subscriber.OnError(e),
                            () => { if (subscriptionsCount > 0) subscriber.OnCompleted(); }
                        );
                }
            }
            return isRunning;
        });
        return raw.Publish();
    }
}

答案 2 :(得分:3)

这是我的答案。我相信暂停恢复可能存在竞争条件,但是可以通过将所有活动序列化到调度程序来减轻这种情况。 (赞成序列化而不是同步)。

using System;
using System.Reactive.Concurrency;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using Microsoft.Reactive.Testing;
using NUnit.Framework;

namespace StackOverflow.Tests.Q7620182_PauseResume
{
    [TestFixture]
    public class PauseAndResumeTests
    {
        [Test]
        public void Should_pause_and_resume()
        {
            //Arrange
            var scheduler = new TestScheduler();

            var isRunningTrigger = new BehaviorSubject<bool>(true);
            Action pause = () => isRunningTrigger.OnNext(false);
            Action resume = () => isRunningTrigger.OnNext(true);

            var source = scheduler.CreateHotObservable(
                ReactiveTest.OnNext(0.1.Seconds(), 1),
                ReactiveTest.OnNext(2.0.Seconds(), 2),
                ReactiveTest.OnNext(4.0.Seconds(), 3),
                ReactiveTest.OnNext(6.0.Seconds(), 4),
                ReactiveTest.OnNext(8.0.Seconds(), 5));

            scheduler.Schedule(TimeSpan.FromSeconds(0.5), () => { pause(); });
            scheduler.Schedule(TimeSpan.FromSeconds(5.0), () => { resume(); });



            //Act
            var sut = Observable.Create<IObservable<int>>(o =>
            {
                var current = source.Replay();
                var connection = new SerialDisposable();
                connection.Disposable = current.Connect();

                return isRunningTrigger
                    .DistinctUntilChanged()
                    .Select(isRunning =>
                    {
                        if (isRunning)
                        {
                                //Return the current replayed values.
                                return current;
                        }
                        else
                        {
                                //Disconnect and replace current.
                                current = source.Replay();
                                connection.Disposable = current.Connect();
                                //yield silence until the next time we resume.
                                return Observable.Never<int>();
                        }

                    })
                    .Subscribe(o);
            }).Switch();

            var observer = scheduler.CreateObserver<int>();
            using (sut.Subscribe(observer))
            {
                scheduler.Start();
            }

            //Assert
            var expected = new[]
            {
                    ReactiveTest.OnNext(0.1.Seconds(), 1),
                    ReactiveTest.OnNext(5.0.Seconds(), 2),
                    ReactiveTest.OnNext(5.0.Seconds(), 3),
                    ReactiveTest.OnNext(6.0.Seconds(), 4),
                    ReactiveTest.OnNext(8.0.Seconds(), 5)
                };
            CollectionAssert.AreEqual(expected, observer.Messages);
        }
    }
}

答案 3 :(得分:-1)

它只是起作用:

    class SimpleWaitPulse
    {
      static readonly object _locker = new object();
      static bool _go;

      static void Main()
      {                                // The new thread will block
        new Thread (Work).Start();     // because _go==false.

        Console.ReadLine();            // Wait for user to hit Enter

        lock (_locker)                 // Let's now wake up the thread by
        {                              // setting _go=true and pulsing.
          _go = true;
          Monitor.Pulse (_locker);
        }
      }

      static void Work()
      {
        lock (_locker)
          while (!_go)
            Monitor.Wait (_locker);    // Lock is released while we’re waiting

        Console.WriteLine ("Woken!!!");
      }
    }

请参阅How to Use Wait and Pulse了解详情