Asynchronous method and collection binding

Posted on

Problem

I have a WPF application, in which I’d like to bind a collection to a combobox using an asynchronous method :

<ComboBox  Margin="2,0,5,0" Width="178" ItemsSource="{Binding Animateur}" DisplayMemberPath="nom"   SelectedIndex="0"  />

the viewmodel class

_service.GetAnimateur((item, error) =>
                      {
                          if (error != null)
                          {
                              // TODO : traitement d'erreur
                          }
                          else
                          {
                              _Animateur.Clear();
                              item.ForEach(Elem =>
                              {
                                  _Animateur.Add(Elem);
                              });

                          }
                      });

the Asynchrounous method :

public async void GetAnimateur(Action<List<fiche>, Exception> callback)
        {
            try
            {
                Task<List<fiche>> data = (Task<List<fiche>>)Task.Run(
                    () =>
                    {
                        DataEntities _db = new DataEntities();
                        var dpcs = _db.fiche;
                        return new List<fiche>(dpcs);
                    });
                var result = await data;
                callback(result, null);
            }
            catch (Exception ex)
            {
                callback(null, ex);
            } 
        }

I have 20128 items in the table fiche, the problem is that GetAnimateur takes a lot of time to fill the combobox.

  1. What are the errors that I commited in this code?
  2. How can I improve it?

Solution

First, I want to point out the behavior of method that is declared as async void. When the code reaches this line:

var result = await data;

The code following after _service.GetAnimateur is executed. async void is there only for event handlers. So using this signature creates more problems than it solves.

Second, I may not see the whole picture, but your GetAnimateur looks weird. I think it is because the problem I’ve mentioned earlier. The most readable and understandable may be code like:

public Task<IEnumerable<fiche>> GetAnimateur()
{
        return Task.Factory.StartNew(() =>
            {
                DataEntities _db = new DataEntities();
                return (IEnumerable<fiche>) _db.fiche;
            });
}

and consume like:

try
{
    var list = await GetAmateur();
    // success
}
catch (Exception ex)
{
    // fail
}

or even:

GetAmateur().ContinueWith(t =>
    {
        if (t.IsCompleted)
        {
            var list = t.Result;
            // success
        }
        else
        {
            var ex = t.Exception;
            // fail
        }
    }, TaskScheduler.FromCurrentSynchronizationContext());

Third, when you add or remove an item from ObservableCollection, the UI gets updated, slowing down performance. One common way to create an observable collection that supports painless multi-adding is

public class RangeObservableCollection<T> : ObservableCollection<T>
{
    private bool _suppressNotification = false;

    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        if (!_suppressNotification)
            base.OnCollectionChanged(e);
    }

    public void AddRange(IEnumerable<T> list)
    {
        if (list == null)
            throw new ArgumentNullException("list");

        _suppressNotification = true;

        foreach (T item in list)
        {
            Add(item);
        }
        _suppressNotification = false;
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }
}

So you simply use RangeObservableCollection instead of ObservableCollection.

Fourth, just don’t forget about tuning your EF properly for better performance.

Leave a Reply

Your email address will not be published. Required fields are marked *