Observable not reacting to blocking collection changed on different thread

半世苍凉 提交于 2019-12-11 09:16:43

问题


I have the following code:

class Program
{
    static void Main(string[] args)
    {
        var watcher = new SNotifier(DumpToConsole);
        watcher.StartQueue();

        Console.ReadLine();
    }

    private static void DumpToConsole(IList<Timestamped<int>> currentCol)
    {
        Console.WriteLine("buffer time elapsed, current collection contents is: {0} items.", currentCol.Count);
        Console.WriteLine("holder has: {0}", currentCol.Count);
    }
}

the SNotifier:

public class SNotifier
{
    private BlockingCollection<int> _holderQueue;
    private readonly Action<IList<Timestamped<int>>> _dumpAction;

    public SNotifier(Action<IList<Timestamped<int>>> dumpAction)
    {
        PopulateListWithStartValues();
        _dumpAction = dumpAction;
    }

    public void StartQueue()
    {
        PopulateQueueOnDiffThread();

        var observableCollection = _holderQueue.ToObservable();

        var myCollectionTimestamped = observableCollection.Timestamp();
        var bufferedTimestampedCollection = myCollectionTimestamped.Buffer(TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(3));

        using (bufferedTimestampedCollection.Subscribe(_dumpAction))
        {
            Console.WriteLine("started observing collection");
        }
    }

    private void PopulateQueueOnDiffThread()
    {
        Action addToCollectionAction = AddToCollection;
        var t = new TaskFactory();
        t.StartNew(addToCollectionAction);

    }

    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;
    }

    private void AddToCollection()
    {
        while (true)
        {
            var newElement = new Random().Next(1, 10);
            _holderQueue.Add(newElement);
            Console.WriteLine("added {0}", newElement);
            Console.WriteLine("holder has: {0}", _holderQueue.Count);
            Thread.Sleep(1000);
        }
    }

    private void PopulateListWithStartValues()
    {
        _holderQueue = new BlockingCollection<int>();
        var elements = GetInitialElements();
        foreach (var i in elements)
            _holderQueue.Add(i);
    }
}

I need to run the DumpToConsole() method to show the collection count every 3 seconds, while this collection is having its contents changed on another thread. My problem is that DumpToConsole() is only called once. Why is that?! Ive spent already the entire day on this. Since I've subscribed with my dump method to the observable, it should "observe" the collection changes and re-call the DumpToConsole() method every 3 seconds; thats what I need.

Ideas? Thanks

(P.S. the action passed to the SNotifier class is my way of removing console related stuff in the SNotifier, i'll need to refactor that better, it can be ignored as it has nothing to do with the problem itself)


回答1:


You are calling ToObservable() on your BlockingCollection<int>. This extension method simply takes the IEnumerable<int> interface on the collection and converts it to an IObservable<int>. This has the effect of getting a list of the contents of the collection at the point of subscription and dumping them out via an Observable stream.

It will not continue to enumerate items as they are added.

Use GetConsumingEnumerable() in front of ToObservable() would address this.

However, caution is required as this will also remove items from the collection though, which might not be desirable.

If this is acceptable, you may want to publish the resulting observable in the case of multiple subscribers to avoid wreaking havoc.

If you are just adding, you could consider turning the whole thing around - use a Subject to back an "Add" method and have one subscriber fill a List (or BlockingCollection if you need that) to track the collection, and a second subscriber can then report on progress.

Another approach would be to use an ObservableCollection and subscribe to its events.

In the last two suggestions, you would need to make your "Add" thread-safe since neither Subject<T> nor ObservableCollection<T> are thread-safe themselves.

Addendum

Brandon's comment that you are disposing the subscription in StartQueue made me realise another problem - StartQueue will never return! This is because a call to Subscribe made on a ToObservable() conversion of an IEnumerable will not return until the enumeration has completed - it also therefore holds up disposal (since the IDisposable is the return value of Subscribe) which is why I didn't notice the using @Brandon pointed out either!

With the above two points, you need to make the following additional changes. First, remove the using statement around the subscription, the implicit disposal would cancel the subscription. When we solve the blocking Subscribe call this would then cause the subscription to be immediately cancelled. You should preserve the IDisposable handle if you do need to explicitly unsubscribe at some point.

Second, add a call to SubscribeOn(Scheduler.Default) immediately after the ToObservable() to prevent the Subscribe call blocking.



来源:https://stackoverflow.com/questions/27988120/observable-not-reacting-to-blocking-collection-changed-on-different-thread

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!