Invalid cross-thread access with MVVM, ObservableCollection and Reactive Extensions (Rx) - c#

I'm currently working on Windows Phone applications and I would like to use Reactive Extensions to create asynchronism to have a better UI experience.
I use the MVVM pattern: my View has a ListBox binded in my ViewModel to an ObservableCollection of Items. An Item has traditional properties like Name or IsSelected.
<ListBox SelectionMode="Multiple" ItemsSource="{Binding Checklist, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Content="{Binding Name}" IsChecked="{Binding IsSelected, Mode=TwoWay}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I instantiate my MainViewModel in App.xaml.cs:
public partial class App : Application
{
private static MainViewModel _viewModel = null;
public static MainViewModel ViewModel
{
get
{
if (_viewModel == null)
_viewModel = new MainViewModel();
return _viewModel;
}
}
[...]
}
And I load my data in MainPage_Loaded.
public partial class MainPage : PhoneApplicationPage
{
public MainPage()
{
InitializeComponent();
this.DataContext = App.ViewModel;
this.Loaded += new System.Windows.RoutedEventHandler(MainPage_Loaded);
}
private void MainPage_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
App.ViewModel.LoadData();
}
}
I load my data from my ViewModel (I'm going to move that code to the Model later):
public class MainViewModel : ViewModelBase
{
public ObservableCollection<Item> _checklist;
public ObservableCollection<Item> Checklist
{
get
{
return this._checklist;
}
set
{
if (this._checklist != value)
{
this._checklist = value;
}
}
}
private const string _connectionString = #"isostore:/ItemDB.sdf";
public void LoadData()
{
using (ItemDataContext context = new ItemDataContext(_connectionString))
{
if (!context.DatabaseExists())
{
// Create database if it doesn't exist
context.CreateDatabase();
}
if (context.Items.Count() == 0)
{
[Read my data in a XML file for example]
// Save changes to the database
context.SubmitChanges();
}
var contextItems = from i in context.Items
select i;
foreach (Item it in contextItems)
{
this.Checklist.Add(it);
}
}
}
[...]
}
And it works fine, items are updated in the View.
Now I want to create asynchronism. With a traditional BeginInvoke in a new method that I call from the View instead of LoadData it works fine.
public partial class MainPage : PhoneApplicationPage
{
[...]
private void MainPage_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
App.ViewModel.GetData();
}
}
I use a property that I called CurrentDispatcher, it is filled in the App.xaml.cs with App.Current.RootVisual.Dispatcher.
public class MainViewModel : ViewModelBase
{
[...]
public Dispatcher CurrentDispatcher { get; set; }
public void GetData()
{
this.CurrentDispatcher.BeginInvoke(new Action(LoadData));
}
[...]
}
But I would like to use Reactive Extentions. So I tried different elements of Rx like ToAsync or ToObservable for example but I had some "UnauthorizedAccessException was unhandled" with "Invalid cross-thread access" when I add an item to the Checklist.
I tried to ObserveOn other threads cause maybe the error comes from mix between the UI and the background threads but it doesn't work. Maybe I don't use Rx like I would be in that particular case?
Any help would be much appreciate.
EDIT after your answers:
Here is a code which works great!
public void GetData()
{
Observable.Start(() => LoadData())
.ObserveOnDispatcher()
.Subscribe(list =>
{
foreach (Item it in list)
{
this.Checklist.Add(it);
}
});
}
public ObservableCollection<Item> LoadData()
{
var results = new ObservableCollection<Item>();
using (ItemDataContext context = new ItemDataContext(_connectionString))
{
//Loading
var contextItems = from i in context.Items
select i;
foreach (Item it in contextItems)
{
results.Add(it);
}
}
return results;
}
As you see I didn't use correctly before. Now I can use a ObservableCollection and use it in the Susbscribe. It's perfect! Thanks a lot!

There appears to be no asynchronicity in your code (save the BeginInvoke, which I dont think is doing what you think it is doing).
I assume that you want to use Rx because your code is all blocking at the moment and the UI is unresponsive while the connection to the database is made and the data is loaded :-(
What you want to do is perform the "heavy lifting" on a background thread and then once you have the values just add them to the ObservableCollection on the Dispatcher. You can do this one of three ways:
Return the entire collection in one go. You then loop through the list with a foreach loop adding to the ObservableCollection. This has the potential downside of blocking the UI (unresponsive app) if the list is too large
Return the collection one value at a time, adding each item to the ObservableCollection in an independent call to the dispatcher. This will keep the UI responsive but can take much longer to complete
Return the collection in buffered chunks and try to get the best of both worlds
The code you want may look like this
public IList<Item> FetchData()
{
using (ItemDataContext context = new ItemDataContext(_connectionString))
{
//....
var results = new List<Item>();
foreach (Item it in contextItems)
{
results.Add(it);
}
return results;
}
}
public void LoadData()
{
Observable.Start(()=>FetchData())
.ObserveOnDispatcher()
.Subscribe(list=>
{
foreach (Item it in contextItems)
{
this.Checklist.Add(it);
}
});
}
The code ain't ideal for Unit testing, but it appears that this is not of interest to you any way (static members, VM with DB connectsions etc..) so this might just work?!

I just ran into this myself. There are multiple ways to get to the dispatcher, I had to use the following. I call this directly from my ViewModels (no passing in from outside).
Application.Current.Dispatcher.Invoke(action);

Related

ObservableCollection showing up in UI, but slow in Xamarin

There is a page where the user selects parameters to show the proper collection then on button click jumps to the next page (Coll) where it should show up.
User Selection Page XAML:
<ContentPage.BindingContext><xyz:UserSelectionViewModel</ContentPage.BindingContext>
...
<Button x:Name="Start" Command="{Binding LoadData}" Pressed="StartClick"/>
User Selection Page C#:
private async void ButtonClick(object sender, EventArgs e)
{
var vm = (CollViewModel)BindingContext;
vm.Hard = HardButtonSelected == Hard;
...
vm.Subject = vm.Subject.ToLower();
}
UserSelectionViewModel:
public class UserSelectionViewModel : BaseViewModel
{
public UserSelectionViewModel()
{
_dataStore = DependencyService.Get<IDataStore>();
_pageService = DependencyService.Get<IPageService>();
LoadData= new AsyncAwaitBestPractices.MVVM.AsyncCommand(FilterData);
FilteredData = new ObservableRangeCollection<Items>();
}
public async Task FilterData()
{
FilteredData.Clear();
var filtereddata = await _dataStore.SearchData(Hard, Subject).ConfigureAwait(false);
FilteredData.AddRange(filtereddata);
OnPropertyChanged("FilteredData");
Debug.WriteLine(FilteredData.Count());
await Device.InvokeOnMainThreadAsync(() => _pageService.PushAsync(new Coll(FilteredData)));
}
}
Coll XAML:
<ContentPage.BindingContext><xyz:CollViewModel</ContentPage.BindingContext>
...
<CarouselView ItemsSource="{Binding Source={RelativeSource AncestorType={x:Type z:Coll}}, Path=InheritedData}" ItemTemplate="{StaticResource CollTemplateSelector}">
...
Coll C#:
public partial class Coll : ContentPage
{
public ObservableRangeCollection<Feladatok> InheritedData { get; set; }
public Coll(ObservableRangeCollection<Feladatok> x)
{
InitializeComponent();
InheritedData = x;
OnPropertyChanged(nameof(InheritedData));
}
}
CollViewModel:
public class CollViewModel : UserSelectionViewModel { ... }
BaseViewModel:
private ObservableRangeCollection<Feladatok> inheriteddata;
public ObservableRangeCollection<Feladatok> InheritedData
{
get
{
return inheriteddata;
}
set
{
if (value != inheriteddata)
{
inheriteddata = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("InheritedData"));
}
}
}
Managed to make it work like this with the help of Jason's tips. My only concern remaining is that: Won't this slow down the page that I load the observable collection two times basically? Is it a good practice as I have made it?
Eventually set the BindingContext to the VM and Binding from there. I still feel like it could be done more efficently or maybe that's how it is done. ViewModels are still new for me and I feel like it's much more code and slower with it. But I will close this, as it is working now.

WPF - DataGrid doesn't show list

My goal is to output a list in a datagrid, but this doesn't work and the datagrid is empty.
I tried to display the list in an other way and it did (but I can't remember what it was) and it worked, except for it not being in a datagrid but just data. I have changed up some things, but back then it reached the end and got displayed.
ViewModel in Mainwindow:
public class ViewModel
{
public List<ssearch> Items { get; set; }
private static ViewModel _instance = new ViewModel();
public static ViewModel Instance { get { return _instance; } }
}
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
//For simplicity, let's say this window opens right away
var Mdata = new MDataWindow { DataContext = DataContext };
Mdata.Show();
}
Other Window for data display:
string searchParam = "status = 1";
public MDataWindow()
{
InitializeComponent();
}
private void AButton_Click(object sender, RoutedEventArgs e)
{
MainWindow.ViewModel.Instance.Items = Search(searchParam);
}
public List<ssearch> Search(string where)
{
{
//Lots of stuff going on here
}
return returnList;
}
And in WPF:
<Window x:Class="WPFClient.MDataWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WPFClient"
mc:Ignorable="d"
Title="MDataWindow" Height="Auto" Width="Auto">
<StackPanel>
<Button x:Name="AButton" Click="AButton_Click" Content="Load" />
<DataGrid ItemsSource="{Binding Items}" />
</StackPanel>
</Window>
I have no clue where the error is and tried to strip the code down as much as possible without killing error sources. The Datagrid just stays empty when I press the "Load" button.
EDIT:
I tried to convert the list into an observableColletion before passing it to the ViewModel, but this didn't work. I am working with a library, which I am not sure how to use observableCollection with, so I converted it instead of using it right away:
VM:
public ObservableCollection<Product> Items { get; set; }
Data Window:
List<Product> pp = Search_Products(searchParam);
var oc = new ObservableCollection<Product>(pp);
MainWindow.ViewModel.Instance.Items = oc;
First, change your List<Product> to an ObservableCollection<Product> as this will help to display the Items of the list on Add/Remove immediately.
This is because ObservableCollection implements the INotifyCollectionChanged interface to notify your target(DataGrid) to which it is bound, to update its UI.
Second, your binding can never work as expected due to changed reference of your collection.
private void AButton_Click(object sender, RoutedEventArgs e)
{
// You are changing your Items' reference completely here, the XAML binding
// in your View is still bound to the old reference, that is why you're seeing nothing.
//MainWindow.ViewModel.Instance.Items = Search(searchParam);
var searchResults = Search(searchParam);
foreach(var searchResult in searchResults)
{
MainWindow.ViewModel.Instance.Items.Add(searchResult);
}
}
Make sure you have changed the List to ObservableCollection upon running the Add loop, else you will get an exception saying the item collection state is inconsistent.
The ViewModel class should implement the INotifyPropertyChanged interface and raise its PropertyChanged event whenever Items is set to a new collection:
public class ViewModel : INotifyPropertyChanged
{
private List<ssearch> _items;
public List<ssearch> Items
{
get { return _items; }
set { _items = value; OnPropertyChanged(); }
}
private static ViewModel _instance = new ViewModel();
public static ViewModel Instance { get { return _instance; } }
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
This is required to notify the view regardless of the type of Items.
If you change the type of Items to ObservableCollection<T>, you should initialize the collection in the view model once:
public class ViewModel
{
public ObservableCollection<ssearch> Items { get; } = new ObservableCollection<ssearch>();
private static ViewModel _instance = new ViewModel();
public static ViewModel Instance { get { return _instance; } }
}
...and then add items to this collection instead of setting the property to a new one:
private void AButton_Click(object sender, RoutedEventArgs e)
{
MainWindow.ViewModel.Instance.Items.Clear();
var search = Search(searchParam);
if (search != null)
foreach (var x in search)
MainWindow.ViewModel.Instance.Items.Add(x);
}

How do I bind a Picker async?

I'm loading via httpClient some values into a List. Now I want to bind this List to a Picker. But the Picker is empty.
I have a class "Trade" with different items, e.g. title.
The ViewModel (FirmsViewModel) has the following code:
public async Task GetTradesData()
{
var tradeList = await App.RestService.GetTradesAsync(true);
Trades = new ObservableCollection<Trade>(tradeList);
}
The "Trades" List is filled. Till this point it seems to be working.
In my Page.cs file I have the following code:
public FirmsPage()
{
InitializeComponent();
viewModel = new FirmsViewModel();
BindingContext = viewModel;
}
protected async override void OnAppearing()
{
base.OnAppearing();
await viewModel.GetTradesData();
}
The XAML of the picker:
<Picker SelectedIndex="{Binding TradesSelectedIndex, Mode=TwoWay}"
ItemsSource="{Binding Trades}"
ItemDisplayBinding="{Binding title}"
Margin="0,15,0,0"
Title="Select a Trade">
</Picker>
If you are running the code, the Picker is always empty. Any suggestions?
Thanks.
That should be straight forward:
Make sure that you fire the PropertyChanged event after setting the Trades property
Make sure that this event is fired on the UI Thread
So if assuming your declaration of Trades looks like:
public ObservableCollection<Trade> Trades { get; private set; }
You could just call. RaisePropertyChanged("Trades"); (or whatever the equivalent is in your ViewModel type) right after assigning it in GetTradesData()
Alternatively you could change your declaration of your property:
private ObservableCollection<Trade> _trades;
public ObservableCollection<Trade> Trades
{
get => _trades;
set
{
_trades = value;
RaisePropertyChanged("Trades");
}
}
Or what I personally would prefer, is to simply initialize the ObservableCollection from the beginning and simply adding the items to it in GetTradesData():
public ObservableCollection<Trade> Trades { get; } = new ObservableCollection<Trade>();
and in GetTradesData():
foreach (var trade in tradeList)
Trades.Add(trade);

Update Observable collection from different UI

I have a WPF application which shows a Folder's contents in a Treeview in the MainWindowView. Now I have a new window where the user can change the location and press reload. Now I want to update the treeview in my MainWindowView as soon as the user presses the Reload button.
I am using an ObservableCollection object which is binded to the treeview. But I am not able to update the collection from the Change location window.
I want to know how to update the ObservableCollection of the MainWindowView from a different window. If I am doing any changes in the MainWindowView, then it immediately reflects in the TreeView
I am using MVVM architecture.
Is there any relationship between the MainWindow and the ChangeLocationWindow?
How does the ChangeLocationWindow show out, Show() or ShowDialog()? Check the following solution, any problems, let me know.
MainWindowViewModel:
public class MainWindowViewModel
{
public static MainWindowViewModel Instance = new MainWindowViewModel();
public ObservableCollection<string> Contents = new ObservableCollection<string>();
public string Location
{
get { return _location; }
set
{
if (_location != value)
{
_location = value;
ReloadContents();
}
}
}
private MainWindowViewModel()
{
}
private void ReloadContents()
{
// fill test data
Contents.Add("Some test data.");
}
private string _location;
}
MainWindowView:
{
public MainWindowView()
{
InitializeComponent();
MyListBox.ItemsSource = MainWindowViewModel.Instance.Contents;
var changeLocationWindow = new ChangeLocationWindow();
changeLocationWindow.Show();
}
}
ChangeLocationWindow:
public partial class ChangeLocationWindow : Window
{
public ChangeLocationWindow()
{
InitializeComponent();
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
MainWindowViewModel.Instance.Location = "Test";
}
}
The best approach to your problem is using Messaging pattern to send notifications to main viewmodel from another one about new changes.
Checkout the link for more details,

Upgrading to .NET 4.5: An ItemsControl is inconsistent with its items source

I'm building an application, which uses many ItemControls(datagrids and listviews). In order to easily update these lists from background threads I used this extension to ObservableCollections, which has worked fine:
http://geekswithblogs.net/NewThingsILearned/archive/2008/01/16/have-worker-thread-update-observablecollection-that-is-bound-to-a.aspx
Today I installed VS12(which in turn installed .NET 4.5), as I want to use a component which is written for .NET 4.5. Before even upgrading my project to .NET 4.5 (from 4.0), my datagrid started throwing InvalidOperationException when updated from a workerthread. Exception message:
This exception was thrown because the generator for control 'System.Windows.Controls.DataGrid Items.Count:5' with name '(unnamed)' has received sequence of CollectionChanged events that do not agree with the current state of the Items collection. The following differences were detected:
Accumulated count 4 is different from actual count 5. [Accumulated count is (Count at last Reset + #Adds - #Removes since last Reset).]
Repro code:
XAML:
<Window x:Class="Test1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid ItemsSource="{Binding Items, Mode=OneTime}" PresentationTraceSources.TraceLevel="High"/>
</Grid>
</Window>
Code:
public partial class MainWindow : Window
{
public ExtendedObservableCollection<int> Items { get; private set; }
public MainWindow()
{
InitializeComponent();
Items = new ExtendedObservableCollection<int>();
DataContext = this;
Loaded += MainWindow_Loaded;
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
Task.Factory.StartNew(() =>
{
foreach (var item in Enumerable.Range(1, 500))
{
Items.Add(item);
}
});
}
}
WPF 4.5 provides some new functionality to access collections on non-UI Threads.
It WPF enables you to access and modify data collections on threads
other than the one that created the collection. This enables you to
use a background thread to receive data from an external source, such
as a database, and display the data on the UI thread. By using another
thread to modify the collection, your user interface remains
responsive to user interaction.
This can be done by using the static method EnableCollectionSynchronization on the BindingOperations class.
If you have a lot of data to collect or modify, you might want to use
a background thread to collect and modify the data so that the user
interface will remain reactive to input. To enable multiple threads to
access a collection, call the EnableCollectionSynchronization method.
When you call this overload of the
EnableCollectionSynchronization(IEnumerable, Object) method, the
system locks the collection when you access it. To specify a callback
to lock the collection yourself, call the
EnableCollectionSynchronization(IEnumerable, Object,
CollectionSynchronizationCallback) overload.
The usage is as follows. Create an object that is used as a lock for the synchronization of the collection. Then call the EnableCollectionSynchronization method of the BindingsOperations and pass to it the collection you want to synchronize and the object that is used for locking.
I have updated your code and added the details. Also i changed the collection to the normal ObservableCollection to avoid conflicts.
public partial class MainWindow : Window{
public ObservableCollection<int> Items { get; private set; }
//lock object for synchronization;
private static object _syncLock = new object();
public MainWindow()
{
InitializeComponent();
Items = new ObservableCollection<int>();
//Enable the cross acces to this collection elsewhere
BindingOperations.EnableCollectionSynchronization(Items, _syncLock);
DataContext = this;
Loaded += MainWindow_Loaded;
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
Task.Factory.StartNew(() =>
{
foreach (var item in Enumerable.Range(1, 500))
{
lock(_syncLock) {
Items.Add(item);
}
}
});
}
}
See also: http://10rem.net/blog/2012/01/20/wpf-45-cross-thread-collection-synchronization-redux
To summarize this topic, this AsyncObservableCollection works with .NET 4 and .NET 4.5 WPF apps.
using System;
using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Windows.Data;
using System.Windows.Threading;
namespace WpfAsyncCollection
{
public class AsyncObservableCollection<T> : ObservableCollection<T>
{
public override event NotifyCollectionChangedEventHandler CollectionChanged;
private static object _syncLock = new object();
public AsyncObservableCollection()
{
enableCollectionSynchronization(this, _syncLock);
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
using (BlockReentrancy())
{
var eh = CollectionChanged;
if (eh == null) return;
var 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);
}
}
}
private static void enableCollectionSynchronization(IEnumerable collection, object lockObject)
{
var method = typeof(BindingOperations).GetMethod("EnableCollectionSynchronization",
new Type[] { typeof(IEnumerable), typeof(object) });
if (method != null)
{
// It's .NET 4.5
method.Invoke(null, new object[] { collection, lockObject });
}
}
}
}
The answer from Jehof is correct.
We cannot yet target 4.5 and had this issue with our custom observable collections that already allowed background updates (by using the Dispatcher during event notifications).
If anyone finds it useful, I have used the following code in our application that targets .NET 4.0 to enable it to use this functionality if the execution environment is .NET 4.5:
public static void EnableCollectionSynchronization(IEnumerable collection, object lockObject)
{
// Equivalent to .NET 4.5:
// BindingOperations.EnableCollectionSynchronization(collection, lockObject);
MethodInfo method = typeof(BindingOperations).GetMethod("EnableCollectionSynchronization", new Type[] { typeof(IEnumerable), typeof(object) });
if (method != null)
{
method.Invoke(null, new object[] { collection, lockObject });
}
}
This is for Windows 10 Version 1607 users using the release version of VS 2017 that may have this issue.
Microsoft Visual Studio Community 2017
Version 15.1 (26403.3) Release
VisualStudio.15.Release/15.1.0+26403.3
Microsoft .NET Framework
Version 4.6.01586
You didn't need the lock nor EnableCollectionSynchronization.
<ListBox x:Name="FontFamilyListBox" SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}" Width="{Binding FontFamilyWidth, Mode=TwoWay}"
SelectedItem="{Binding FontFamilyItem, Mode=TwoWay}"
ItemsSource="{Binding FontFamilyItems}"
diag:PresentationTraceSources.TraceLevel="High">
<ListBox.ItemTemplate>
<DataTemplate DataType="typeData:FontFamilyItem">
<Grid>
<TextBlock Text="{Binding}" diag:PresentationTraceSources.TraceLevel="High"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
public ObservableCollection<string> fontFamilyItems;
public ObservableCollection<string> FontFamilyItems
{
get { return fontFamilyItems; }
set { SetProperty(ref fontFamilyItems, value, nameof(FontFamilyItems)); }
}
public string fontFamilyItem;
public string FontFamilyItem
{
get { return fontFamilyItem; }
set { SetProperty(ref fontFamilyItem, value, nameof(FontFamilyItem)); }
}
private List<string> GetItems()
{
List<string> fonts = new List<string>();
foreach (System.Windows.Media.FontFamily font in Fonts.SystemFontFamilies)
{
fonts.Add(font.Source);
....
other stuff..
}
return fonts;
}
public async void OnFontFamilyViewLoaded(object sender, EventArgs e)
{
DisposableFontFamilyViewLoaded.Dispose();
Task<List<string>> getItemsTask = Task.Factory.StartNew(GetItems);
try
{
foreach (string item in await getItemsTask)
{
FontFamilyItems.Add(item);
}
}
catch (Exception x)
{
throw new Exception("Error - " + x.Message);
}
...
other stuff
}
The other solutions seems a bit excessive, you could just use delegate to keep threads in sync:
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
Task.Factory.StartNew(() =>
{
foreach (var item in Enumerable.Range(1, 500))
{
App.Current.Dispatcher.Invoke((Action)delegate
{
Items.Add(item);
}
}
});
}
This should work just fine.

Categories