Home > Net >  WPF DataGrid and IAsyncEnumerable items
WPF DataGrid and IAsyncEnumerable items

Time:09-26

Is it possible? How?

I have an asynchronous method fetching rows of data from my database. My result is IAsyncEnumerable<TItem>. It should be a perfect data type for a fast and responsive UI, however - when I set it as a datasource for my grid it just doesn't work and shows nothing.

I have 2 workarounds for this. First is obviously to return array from my async task. The second one is similar - I can just use ToArrayAsync() on result (from System.Linq.Async).

I suspect that using ToArrayAsync() is slower than just returning the array from the task, so it doesn't make much sense.

It would make a lot of sense if IAsyncEnumerable type could be used directly as a data source for the control. But then I probably need to implement some kind of IObservable interface or something like that. Did anyone tried this?

The whole thing is pretty new and I can't find any examples, or even an article stating that it's even possible. I mean - what good IAsyncEnumerable is if you cannot use it for the UI?

Update: what I'm looking for is a conversion between IAsyncEnumerable<TItem> and ObservableCollection<TItem>. I found examples, but they all work exactly as my mentioned workaround - so the data is put into an array and then added to the collection. This defeats all IAsyncEnumerable<T> benefits.

CodePudding user response:

IAsyncEnumerable is introduced to allow async data generators/data streams. Usually controls like ItemsControl that display a collection of data items are index based. And ICollectionView based.

Usually the control gets the items via the ICollectionView of the original collection. A data item, identified by its index in the source collection's ICollectionView, is wrapped into an UIElement container for rendering.
A data stream/generator is not index based. An Enumerator (result of a call to IEnumerable.GetEnumerator) is used to step from element to element by invoking IEnumerator.MoveNext (foreach is a language construct that for convenience encapsulates the actual enumeration logic that involves the IEnumerator).
Aside from potential threading problems introduced by an async data stream, the index based ItemsControl needs to know all it's items in advance - that's why you need a collection that implements INotifyCollectionChanged in order to have the ItemsControl update its Items on changes: the View is considered to be passive - it only displays data and does not generate the data.
Although not mandatory, WPF was designed with MVVM in mind. The active part is usually the Model or the View Model. Data generation would usually take place in the Model. So, it does not make sense to have IAsyncEnumerable as direct binding source as this would mean that the passive UI element must generate/consume the data stream directly. You would want this to be a responsibility of the View Model/Binding.Source. And again, the UI is designed to work on ICollectionView implementations and not on collections and their iterators directly.

What you can do is to enumerate the IAsyncEnumerable in the Binding.Source and update an ObservableCollection with the generated results:

public ObservableCollection<DataItem> ItemsSourceForDataGrid { get; }

private async Task GenerateDataItemsAsync(IAsyncEnumerable asyncDataStream)
{
  // Keep the GUI responsive while consuming an e.g. CPU intensive generator
  await foreach (DataItem item in asyncDataStream)
  {
    this.ItemsSourceForDataGrid.Add(item);
  }
}

Also note that extension methods like ToListAsync and ToArrayAsync can potentially lock the application. If you don't know the data source i.e. the generator you risk to lock your application as ToListAsync tries to finalize the data stream. But per definition a data stream can be infinite which would cause ToListAsync to never return or complete the returned Task object. If you find yourself in the need of ToArrayAsync and you are the author of the data generator, you probably should not use IAsyncEnumerable in the first place. Asynchronous finalizers like ToArrayAsync defy the idea of IAsyncEnumerable.

  • Related