update an ObservableCollection with a BlockingCollection
Asked Answered
P

2

6

I subscribe to a service that will raise an Event when a new element is received, I add this element to a BlockingCollection.
I have a second thread running that will loop the BlockingCollection to add/update the element in an observable collection.

The problem is how do you do add in the ObservableCollection? I know I can't just do an .add on this type of collection, as it needs to be done on the UI thread. So I tried using different ObservableCollection subclass that use the dispatcher to marshal the adding of element, but every single time, I end up with the same error

"An unhandled exception of type 'System.StackOverflowException' occurred in Unknown Module."

with a troubleshooting tips as:

make sure you don't have an infinite loop or inifiniterecursion.

Well the thing is, I do have some kind of infinite loop actually, as the BlockingQueue is receiving new elements all the time, like 5 to 10 per sec.
I won't get the exception if I don't bind a control to the observablecollection though, or if I use a List instead.

Class ElementHolder
{
    private ExternalService _externalService;
    private ObservableCollection<Element> _elementsList = new ObservableCollection<Element>();
    private BlockingCollection<Element> _elementsReceived = new BlockingCollection<Element>();

    public ObservableCollection<Element> ElementsList
    {
        get
        {
            return _elementList;
        }
        set
        {
            _elementList = value;
        }
    }

public ElementHolder()
    {
        Task.Factory.StartNew(ReadElements);
        _externalService = new ExternalService();
        _externalService.ReceivedNewElement += new Action<Element>(o => _elementsReceived.Add(o));
        _externalService.Subscribe();
    }

private void ReadElements()
    {
        foreach (Element element in _elementsReceived.GetConsumingEnumerable())
        {
            Element item = _elementsList.FirstOrDefault(o => o.ID == element.ID);
            if (item == null)
            {
                _elementList.Add(element);
            }
            else
            {
                item.Update(element);
            }
        }
    }

EDIT The bug disappeared by itself, when i was tracking it down. I was trying to make things simpler to really understand where the issue was, and then it started to work. When putting things back together it still works... BUT it comes back time to time, for what seems unrelated reason like adding a style to my listview. I'm starting to think there's an issue in the third party dll.

Phototelegraph answered 5/11, 2012 at 14:27 Comment(0)
T
3

This is a perfect example of where the Reactive Extensions are a very useful and ingenious tool. There is a pretty steep learning curve to using them, but since you have a specific case here, I will insert the reactive code that will achieve your goal, assuming I understand your goal correctly.

Note that you will need to install the Reactive Extensions, and you will need two using statements (System.Reactive.Linq and System.Reactive.Subjects) and you will need to reference System.Reactive and System.Reactive.Windows.Threading dlls. See Reactive on MSDN.

class ElementHolder
{
    public ObservableCollection<Element> ElementsList { get; set; }
    private ExternalService _externalService = new ExternalService();
    private IDisposable _elementSubscription;
    private Subject<Element> _elementSubject = new Subject<Element>();

    public ElementHolder()
    {
        _externalService.ReceivedNewElement += _elementSubject.OnNext;
        _externalService.Subscribe();

        ElementList = new ObservableCollection<Element>();
        _elementSubscription = _externalService.ObserveOnDispatcher().Subscribe(NextElement);
    }

    private void NextElement(Element e)
    {
        Element item = ElementsList.FirstOrDefault(o => o.ID == element.ID);
        if (item == null) {
            _elementList.Add(element);
        }
        else {
            item.Update(element);
        }
    }
}
Tenrec answered 5/11, 2012 at 15:8 Comment(3)
i had a quick look on RX today actually, and bookmarked introtorx.com as it looks really interesting. But unfortunately in this case, the external service is provided in a dll by a third party, and i can change itPhototelegraph
There are ways you can still do this with Reactive, I'll post back with some code, but you can use the FromEventPattern constructor. Or just shift the code over to your class. It will still work.Tenrec
thx for your help, i won't have time to test this right now, as i need to rush to finish this and now it's working, but i will definitively have a deeper look this week endPhototelegraph
E
0

There's a small error in the answer. See the corrected line below:

_elementSubscription = _elementSubject.ObserveOnDispatcher().Subscribe(NextElement);

It took me a while to figure this out, but rmayer06's answer solved my problem. I can't comment on answers or I would have.

Expiable answered 24/2, 2013 at 20:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.