Rajat Saxena
Rajat Saxena

Reputation: 3925

Updating Listbox results in " Invalid cross-thread access."

I am building a rss feed reader.I created a starter app using Visual studio.On it's main page I added a link to a new pivot page.All the rss thing happens in my pivot page.Now in my rss feed listbox,I initially set some list items using the following code:

public PivotPage1()
{
    InitializeComponent();
    getMeTheNews();
    addToCollection("Android is going up","TechCrunch");
    lstBox.DataContext = theCollection;
}

private void addToCollection(string p1, string p2)
{
    theCollection.Add(new NewsArticle(p1,p2));
}

Here are the other two functions where rss is fetched from the server and parsed,But when I want to add processed entries to the ObservableCollection in getTheResponse() method,it results in the invalid cross thread access error.Any ideas?

Code:

private void getMeTheNews()
{
    String url = "http://rss.cnn.com/rss/edition.rss";
    HttpWebRequest webRequest = (HttpWebRequest)HttpWebRequest.Create(url);
    webRequest.BeginGetResponse(getTheResponse, webRequest);
}

private void getTheResponse(IAsyncResult result)
{
    HttpWebRequest request = result.AsyncState as HttpWebRequest;
    if (request != null)
    {
        try
        {
            WebResponse response = request.EndGetResponse(result);
            XDocument doc = XDocument.Load(response.GetResponseStream());
            IEnumerable<XElement> articles = doc.Descendants("item");
            foreach (var article in articles) {
                System.Diagnostics.Debug.WriteLine(article);
                try
                {
                    System.Diagnostics.Debug.WriteLine(article.Element("title").Value);
                    addToCollection(article.Element("title").Value,"CNN");
                }
                catch (NullReferenceException e) {
                    System.Diagnostics.Debug.WriteLine(e.StackTrace);
                }
            }

        }
        catch(WebException e) {
            System.Diagnostics.Debug.WriteLine(e.StackTrace.ToString());
        }
    }
    else 
    { 
    }
}

Upvotes: 0

Views: 493

Answers (3)

Ricky
Ricky

Reputation: 103

You can use a Dispatcher starting from ObservableCollection with the ovverride of method OnCollectionChanged

public class MyObservableCollectionOverrideCollChangOfObjects<T> : ObservableCollection<T>
    {
        ...

#region CollectionChanged
public override event NotifyCollectionChangedEventHandler CollectionChanged;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
    var eh = CollectionChanged;
    if (eh != null)
    {
        Dispatcher dispatcher = (from NotifyCollectionChangedEventHandler nh in eh.GetInvocationList()
                                    let dpo = nh.Target as DispatcherObject
                                    where dpo != null
                                    select dpo.Dispatcher).FirstOrDefault();

        if (dispatcher != null && dispatcher.CheckAccess() == false)
        {
            dispatcher.Invoke(DispatcherPriority.DataBind, (Action)(() => OnCollectionChanged(e)));
        }
        else
        {
            foreach (NotifyCollectionChangedEventHandler nh in eh.GetInvocationList())
                nh.Invoke(this, e);
        }
    }
}
#endregion
..
}

Upvotes: 0

Ricky
Ricky

Reputation: 103

Cross-Thread Collection Synchronization

Putting a ListBox binding to a ObservableCollection , when the data changes , you update the ListBox because INotifyCollectionChanged implemented . The defect dell'ObservableCollection is that the data can be changed only by the thread that created it.

The SynchronizedCollection does not have the problem of Multi-Thread but does not update the ListBox because it is not implemented INotifyCollectionChanged , even if you implement INotifyCollectionChanged , CollectionChanged (this, e) can only be called from the thread that created it .. so it does not work.

Conclusion

-If you want a list that is autoupdated mono-thread use ObservableCollection

-If you want a list that is not autoupdated but multi-threaded use SynchronizedCollection

-If you want both, use Framework 4.5, BindingOperations.EnableCollectionSynchronization and ObservableCollection () in this way :

/ / Creates the lock object somewhere
private static object _lock = new object () ;
...
/ / Enable the cross acces to this collection elsewhere
BindingOperations.EnableCollectionSynchronization ( _persons , _lock )

The Complete Sample http://10rem.net/blog/2012/01/20/wpf-45-cross-thread-collection-synchronization-redux

Upvotes: 0

Olivier Payen
Olivier Payen

Reputation: 15268

You need to use the Dispatcher to access the Control from a non-UI thread:

Deployment.Current.Dispatcher.BeginInvoke(()=>
{ 
     addToCollection(article.Element("title").Value,"CNN");   
});

Upvotes: 2

Related Questions