I have WPF project with DataGrid. I use MVVM pattern. It's part of my VM:
class LibraryViewModel
{
#region Members
//private SimpleLibDBEntities _database;
ObservableCollection<BooksViewModel> _books = new ObservableCollection<BooksViewModel>();
int count = 0;
int sizeOfdb = 1000000;
#endregion
public ObservableCollection<BooksViewModel> Books
{
get
{
return _books;
}
set
{
_books = value;
}
}
public LibraryViewModel()
{
Task task = Task.Factory.StartNew(Generator);
}
private void Generator()
{
for (count = 0; count < sizeOfdb; count++)
{
_books.Add(new BooksViewModel { Book = new BooksSet { Id = count, Title = "Title"+count, Author = "Author", Publisher = "Publisher", Year = 1000, Note = "Note" } });
}
}
It works, but my DataGrid show me only about 50 000 -100 000 elements(random) instead my int sizeOfdb = 1000000 element. Why it works so? How to repair it?(without "Task" everything work fine)
And how I may to use async/await in this example? Something like that? (not work. try to use Dispathcher? )
public LibraryViewModel()
{
GeneratorAsync();
}
private async void GeneratorAsync()
{
await Task.Factory.StartNew(()=>{
for (count = 0; count < sizeOfdb; count++)
{
_books.Add(...);
}
});
}
The basic problem here is that ObservableCollection
is not fully thread-safe. To be more specific, modifying the collection from one thread while reading from another might not be safe, and ObservableCollection
raises its CollectionChanged
event on the thread that modifies the collection.
What I am guessing is happening in this instance is that your background task creates a number of elements and adds them to the collection before the UI is created and binding happens. At that point the elements in the collection are added to the DataGrid. But when the next item that is added and the CollectionChanged
event is raised on a thread pool thread, the DataGrid
's event handler now runs on the thread pool thread, violating its thread affinity. This causes an exception which terminates your task (I'm guessing - you should see this if you run under the debugger with break on user-unhandled exceptions turned on).
If you expect that generating the collection items will be expensive (eg involve database access in the real version), you should do that on a background thread then add them to the ObservableCollection
on the UI thread - but this question has a good discussion of this.
You can see that await/async isn't really helping you in your attempted formulation, as you're just trying to wrap a fire-and-forget task start with a fire-and-forget "async void". If you're trying to just move the creation off the UI thread, how about this:
private async void AddBooks()
{
var books = await Task.Run(() => GetBooks());
foreach (var book in books)
{
_books.Add(book);
}
}
private List<BooksViewModel> GetBooks()
{
List<BooksViewModel> books = new List<BooksViewModel>();
for (count = 0; count < sizeOfdb; count++)
{
books.Add(new BooksViewModel { Book = new BooksSet { Id = count, Title = "Title" + count, Author = "Author", Publisher = "Publisher", Year = 1000, Note = "Note" } });
}
return books;
}
In this case the creation is run on a thread pool thread, then the await gets you back to the UI thread.