Observable不响应队列在不同的线程上发生了变化

时间:2015-01-16 12:16:56

标签: multithreading c#-4.0 queue system.reactive observer-pattern

我有以下代码:

static void Main()
    {
        var holderQueue = new ConcurrentQueue<int>(GetInitialElements());

        Action<ConcurrentQueue<int>> addToQueueAction = AddToQueue;
        var observableQueue = holderQueue.ToObservable();
        IScheduler newThreadScheduler = new NewThreadScheduler();

        IObservable<Timestamped<int>> myQueueTimestamped = observableQueue.Timestamp();

        var bufferedTimestampedQueue = myQueueTimestamped.Buffer(TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(3), newThreadScheduler);

        var t = new TaskFactory();
        t.StartNew(() => addToQueueAction(holderQueue));

        using(bufferedTimestampedQueue.SubscribeOn(newThreadScheduler).Subscribe(currentQueue =>
        {
            Console.WriteLine("buffer time elapsed, current queue contents is: {0} items.", currentQueue.Count);
            foreach(var item in currentQueue)
                Console.WriteLine("item {0} at {1}", item.Value, item.Timestamp);

            Console.WriteLine("holderqueue has: {0}", currentQueue.Count);
        }))
        {
            Console.WriteLine("started observing queue");

            Console.ReadLine();
        }
    }

    private static void AddToQueue(ConcurrentQueue<int> concurrentQueue)
    {
        while(true)
        {
            var x = new Random().Next(1, 10);
            concurrentQueue.Enqueue(x);
            Console.WriteLine("added {0}", x);
            Console.WriteLine("crtcount is: {0}", concurrentQueue.Count);
            Thread.Sleep(1000);
        }
    }

    private static IEnumerable<int> GetInitialElements()
    {
        var random = new Random();
        var items = new List<int>();
        for (int i = 0; i < 10; i++)
            items.Add(random.Next(1, 10));

        return items;
    }

意图如下:

holderQueue对象最初填充了一些元素(GetInitialElements),然后在另一个线程上使用更多元素(通过方法AddToQueue)进行了更改,并且observable应该是检测此更改,并通过执行其订阅中的方法,在其时间过去时(因此每3秒)做出相应的反应。

简而言之,我期望的是让Subscribe主体中的代码每3秒执行一次,并向我显示队列中的更改(在不同的线程上更改)。相反,Subscribe主体只执行一次。为什么呢?

由于

2 个答案:

答案 0 :(得分:4)

ToObservable方法需要IEnumerable<T>并将其转换为可观察对象。因此,它会占用您的并发队列并立即枚举它,遍历所有可用项目。您稍后修改队列以添加其他项的事实对从并发队列的IEnumerable<T>实现返回的已枚举GetEnumerator()没有影响。

答案 1 :(得分:1)

根据David Pfeffer的回答,仅使用.ToObserverable()将无法满足您的需求。

但是,当我查看您的代码时,我会看到几件事:

  1. 您使用的是NewThreadScheduler
  2. 您正在通过任务
  3. 添加到队列中
  4. 您使用的是ConcurrentQueue<T>
  5. 如果您改变一些事情,我认为您可以实现您在此处所要做的事情。首先,我认为你实际上在寻找一个BlockingCollection<T>。我知道这似乎不太可能,但你可以让它像一个线程安全的队列一样行动。

    接下来,您已经专门用一个线程来处理NewThreadScheduler,为什么不从队列中进行轮询/拉出?

    最后,如果您使用BlockingCollection<T>.GetConsumingEnumerable(CancellationToken)方法,实际上可以返回并使用.ToObservable()方法!

    让我们看一下重写的代码:

    static void Main()
    {
        //The processing thread. I try to set the the thread name as these tend to be long lived. This helps logs and debugging.
        IScheduler newThreadScheduler = new NewThreadScheduler(ts=>{
            var t =  new Thread(ts);
            t.Name = "QueueReader";
            t.IsBackground = true;
            return t;
        });
    
        //Provide the ability to cancel our work
        var cts = new CancellationTokenSource();
    
        //Use a BlockingCollection<T> instead of a ConcurrentQueue<T>
        var holderQueue = new BlockingCollection<int>();
        foreach (var element in GetInitialElements())
        {
            holderQueue.Add(element);
        }
    
        //The Action that periodically adds items to the queue. Now has cancellation support
        Action<BlockingCollection<int>,CancellationToken> addToQueueAction = AddToQueue;
        var tf = new TaskFactory();
        tf.StartNew(() => addToQueueAction(holderQueue, cts.Token));
    
        //Get a consuming enumerable. MoveNext on this will remove the item from the BlockingCollection<T> effectively making it a queue. 
        //  Calling MoveNext on an empty queue will block until cancelled or an item is added.
        var consumingEnumerable = holderQueue.GetConsumingEnumerable(cts.Token);
    
        //Now we can make this Observable, as the underlying IEnumerbale<T> is a blocking consumer.
        //  Run on the QueueReader/newThreadScheduler thread.
        //  Use CancelationToken instead of IDisposable for single method of cancellation.
        consumingEnumerable.ToObservable(newThreadScheduler)
            .Timestamp()
            .Buffer(TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(3), newThreadScheduler)
            .Subscribe(buffer =>
                {
                    Console.WriteLine("buffer time elapsed, current queue contents is: {0} items.", buffer.Count);
                    foreach(var item in buffer)
                        Console.WriteLine("item {0} at {1}", item.Value, item.Timestamp);
    
                    Console.WriteLine("holderqueue has: {0}", holderQueue.Count);
                },
                cts.Token);
    
    
        Console.WriteLine("started observing queue");
    
        //Run until [Enter] is pressed by user.
        Console.ReadLine();
    
        //Cancel the production of values, the wait on the consuming enumerable and the subscription.
        cts.Cancel();
        Console.WriteLine("Cancelled");
    }
    
    private static void AddToQueue(BlockingCollection<int> input, CancellationToken cancellationToken)
    {
        while(!cancellationToken.IsCancellationRequested)
        {
            var x = new Random().Next(1, 10);
            input.Add(x);
            Console.WriteLine("added '{0}'. Count={1}", x, input.Count);
            Thread.Sleep(1000);
        }
    }
    
    private static IEnumerable<int> GetInitialElements()
    {
        var random = new Random();
        var items = new List<int>();
        for (int i = 0; i < 10; i++)
            items.Add(random.Next(1, 10));
    
        return items;
    }
    

    现在我想你会得到你期待的结果:

    added '9'. Count=11
    started observing queue
    added '4'. Count=1
    added '8'. Count=1
    added '3'. Count=1
    buffer time elapsed, current queue contents is: 14 items.
    item 9 at 25/01/2015 22:25:35 +00:00
    item 5 at 25/01/2015 22:25:35 +00:00
    item 5 at 25/01/2015 22:25:35 +00:00
    item 9 at 25/01/2015 22:25:35 +00:00
    item 7 at 25/01/2015 22:25:35 +00:00
    item 6 at 25/01/2015 22:25:35 +00:00
    item 2 at 25/01/2015 22:25:35 +00:00
    item 2 at 25/01/2015 22:25:35 +00:00
    item 9 at 25/01/2015 22:25:35 +00:00
    item 3 at 25/01/2015 22:25:35 +00:00
    item 9 at 25/01/2015 22:25:35 +00:00
    item 4 at 25/01/2015 22:25:36 +00:00
    item 8 at 25/01/2015 22:25:37 +00:00
    item 3 at 25/01/2015 22:25:38 +00:00
    holderqueue has: 0
    added '7'. Count=1
    added '2'. Count=1
    added '5'. Count=1
    buffer time elapsed, current queue contents is: 3 items.
    item 7 at 25/01/2015 22:25:39 +00:00
    item 2 at 25/01/2015 22:25:40 +00:00
    item 5 at 25/01/2015 22:25:41 +00:00
    holderqueue has: 0
    Cancelled