0

So I want to load my data collections in a background thread and then bind my treeview to the new collection (rather than having the background thread queue things up on the dispatcher every time it wants to add an item to the list [sounds inefficient]).

Is this possible? I create the new data structure and it is output as pd.result on a background thread. When the UI thread checks that the dialog box has closed, it should then set

ModuleHierarchyVM.TopLevelModules = pd.Result as ObservableCollection<ModuleViewModel>;

after this the event OnLoadVCD is called. I have an event handler that then tries to set a treeview's itemsource to the new collection.

this.AvailableModulesTreeView.ItemsSource = gvvm.ModuleHierarchyVM.TopLevelModules;

This crashes with the error: "The calling thread cannot access this object because a different thread owns it."

Not even sure how to debug it, the call stack doesn't give any real details.

However, if I just set the Itemsource to a blank new empty collection like so:

this.AvailableModulesTreeView.ItemsSource = (IEnumerable<object>)new List<object>();

it doesn't crash (but then it's not displaying my data either). Any ideas what could be causing the crash?

I thought it might be that I was updating the UI from the wrong thread, so I tried both calling the dispatcher with begininvoke, and checking that I am indeed the UI thread with dispatcher.checkaccess(). so that does not seem to be the issue. However, I really don't know what is going on.

Another way I could implement this is to just make my parsing routine just update the original data structure that is bound to the treeview by caling dispatcher on each item as it is added to the observable collection. However, even if that is the only solution, I really dislike not knowing why something doesn't work. In my mind, it seems reasonable to just create an entirely new data structure on a different thread, and then rebind the new datastructure to the treeview, discarding the old one. It also seems cleaner to me than dozens of 1 line ObservableCollectionInstance.Add calls being placed on the dispatcher while parsing through a file on the background thread.

Full Code:

method called by UI thread

public bool LoadPortInterface(string VCDFileName)
{
    ProgressDialog pd = new ProgressDialog("Loading File: ", VCDFileName);
    pd.Owner = Application.Current.MainWindow;
    pd.WindowStartupLocation = WindowStartupLocation.CenterOwner;
    ModuleHierarchyVM.TopLevelModules.Clear();


    VCDData TempVCDOutput = null;
    Func<object> handler = delegate
    {
        return VCDParser.ParseVCDFileForAllPorts(VCDFileName, this, pd.Worker, out TempVCDOutput);
    };
    pd.RunWorkerThread(handler);
    pd.ShowDialog();
    if (pd.DialogResult == true)
    {
        ModuleHierarchyVM.TopLevelModules = pd.Result as ObservableCollection<ModuleViewModel>;
        VCDOutput = TempVCDOutput;
    }
    OnLoadVcd();
}

Response to OnLoadVCD event handler in graphviewer:

void gvvm_LoadVCDEvent(object sender, EventArgs e)
{
    this.AvailableModulesTreeView.ItemsSource = gvvm.ModuleHierarchyVM.TopLevelModules;
}
Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188
James Joshua Street
  • 3,259
  • 10
  • 42
  • 80
  • You're passing `this` into the parser. I think we need to see what's going on in that code. Can you post it? – Baldrick Mar 11 '14 at 01:44
  • I'm pretty sure I found my real problem. I tried to call OnPropertyChanged on an observable collection. I'm guessing I was trying to deal with my collection view source not updating by forcing it. But i don't get why it worked for one case without crashing. Anyway now I just need to figure out what I should actually do. One option is to just rebind the newly created Observable collection to the collectionviewsource I have defined in xaml. Or I could pass the collection to the background thread, but have any viewmodel updates get dispatched by the WPF dispatcher. Former is easier I bet – James Joshua Street Mar 11 '14 at 02:12
  • Why not have the parser return a List, then add to a new ObservableCollection in the UI thread when it returns? Does it need to be an ObservableCollection in the background thread? – Baldrick Mar 11 '14 at 02:14
  • No, I will try that and see if it helps. I think the problem may lie with my collectionviewsource though. Is it not possible to update the source of a collectionviewsource that is being used by the UI? – James Joshua Street Mar 11 '14 at 02:34
  • There's no reason why you shouldn't call NotifyPropertyChanged for the TopLevelModules object itself - that should trigger the UI to reload the whole collection with no issues. – Baldrick Mar 11 '14 at 02:52
  • added a lot of code; hopefully it can help people make sense of my problem. so confused – James Joshua Street Mar 11 '14 at 23:00

2 Answers2

2

I think it will be easer to use TPL "Task Parallel Library"

For example, if you want to create new thread you can create it as task.

var task = Task.Factory.StartNew(() =>
{
   // write what you want to do here
}

You can get more examples from this link Task Parallelism (Task Parallel Library)

So, to update the UI from thread, you can run this thread in the same of UI thread as below:

        var uiContext = TaskScheduler.FromCurrentSynchronizationContext();

        _taskFactoryWrapper.StartTask(() => DoSomeWork(viewSettings.AnyValue)).ContinueWith(task =>
        {

                viewSettings.Result= task.Result;

        },TaskContinuationOptions.AttachedToParent)
            .ContinueWith(t => EndingUploadingProgress(viewSettings), uiContext);

So you can create a TaskScheduler associated with the current UI thread.]

Samuel Gerges
  • 251
  • 3
  • 10
1

You are most likely creating, reading or modifying the ObservableCollection on a thread other than the UI thread. Also make sure you are not adding to or removing from the ObservableCollection on anything other than the UI thread.

To debug this, put a breakpoint wherever you access/modify the observable collection and note the thread number (Thread window in VS) that hits that breakpoint. It should always be the same.

You could use another structure (List/Array) to hold the results, then call back to the UI thread to update/create the ObservableCollection. Updating the ObservableCollection is inexpensive, even for hundreds of items.

What does get expensive is that ObservableCollection will raise a change event on every change, which may be handled by the UI components to change their layout, which has to be done on the UI thread anyway. This UI event handling is why ObservableCollection prevents you from modifying across threads.

If you are adding/removing a large number of items, it may be better to create a new collection and reassign the DataSource. If you always do this, you can use a List instead. ObservableCollection is for when you want to modify the list and have the control only change the smallest amount possible. Changing the DataSource (eg to List) will clear the control and rebuild it, which may be better for many changes.

See:

Updating an ObservableCollection in a separate thread

How do I update an ObservableCollection via a worker thread?

What's the best way to update an ObservableCollection from another thread?

Community
  • 1
  • 1
Robert Wagner
  • 17,515
  • 9
  • 56
  • 72
  • wait wait, i am confused. so even if the observable collection is not yet tied to a UI thread, I cannot access it from a non-UI thread? so I have to make it a list in the background thread, and then create an observable collection from that list in the UI thread? this seems super inefficient if the list is really huge. What is the point of running in the background if I'm going to have to create the datastructure in the UI thread to begin with? if this is the case, then maybe i'm better off not using observable collection at all – James Joshua Street Mar 11 '14 at 23:10
  • if i understood you correctly then I think I will just not bother worrying about notifications for this data structure. the thing is that the treeview is only being used to display the item's name property, which should never get changed after the initial load, so I don't really need property change notifications to the treeview. – James Joshua Street Mar 11 '14 at 23:15
  • @JamesJoshuaStreet I suggest you create a List in the background thread to hold all your data, then switch back onto the UI thread and update the observable. I'll update the answer with some more details. – Robert Wagner Mar 11 '14 at 23:21
  • uggh, actually i just realized, i have observable collections of items that contain observable collections. Meaning that my only real solution is to just add to the observable collections using the dispatcher. it seems so silly to have a background thing load things only to create objects using the dispatcher. I have to admit I'm worried though. is the dispatcher guaranteed to do the items in the order that i queue them up? – James Joshua Street Mar 11 '14 at 23:35
  • background thread load things* All this trouble just because I found observable collections easier to use in the single thread case. I am now trying to use: Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() => ParentViewModel.Submodules.Add(NewModuleViewModule) )); everywhere that i modify an observable collection – James Joshua Street Mar 12 '14 at 00:03