I wanted to load data on a screen in the background because I was experiencing UI Freezes on screen. After changing the code I found that that drawing to the screen actually takes a long time. So I wanted to update my gridview asynchronously. This seemed simple but quickly became complicated, let’s see what you need to do:

So suppose we have a viewmodel which looks like this:

public class MainPage : ViewModelBase
{
  private LongRunningCall _something;

  public ObservableCollection<Row> Rows { get; } = new ObservableCollection<Row>();

  public MainPage(LongRunningCall something)
  {
    _something = something;
  }

  public void LoadData()
  {
    var items = await _something.GetData();
    foreach(var item in items)
    {
      Rows.Add(new Row { FirstName = item.FirstName, LastName = item.LastName });
    }
  }
}

Then we use a grid that binds on the rows collection and defines some columns. However what if refreshing the grid/loading the data takes a while. Then our UI freezes which is bad. So what can we do to fix it.

1.Make loading asynchronous

So after reading about Tasks we know that we can load the data asynchronously on screen. So we change it to the following code.

public class MainPage : ViewModelBase
{
  private LongRunningCall _something;

  public ObservableCollection<Row> Rows { get; } = new ObservableCollection<Row>();

  public MainPage(LongRunningCall something)
  {
    _something = something;
  }

  public async void LoadData()
  {
    var items = await _something.GetDataAsync();
    foreach(var item in items)
    {
      Rows.Add(new Row { FirstName = item.FirstName, LastName = item.LastName });
    }
  }
}

This already improves responsibility of the UI because the slow loading is gone. But we can make it even better:

2.Update the UI asynchronous

If you find out that the actually updating on  screen is slow we can make that asynchrounous by changing our code to:

public class MainPage : ViewModelBase
{
  private LongRunningCall _something;

  public ObservableCollection<Row> Rows { get; } = new ObservableCollection<Row>();
  private object _rowLock = new object();

  public MainPage(LongRunningCall something)
  {
    _something = something;
    //Signal WPF that it  needs to enable collection synchronization for this observable collection
    BindingOperations.EnableCollectionSynchronization(Rows, _rowLock);
  }

  public async void LoadData()
  {
    var loadTask = _something.GetDataAsync();

    await loadTask.ContinueWith(async _ =>
     {
       var data = _.Result;
       lock (_rowLock)
       {
         //every time we want to change the collection we need to lock it
         foreach (var item in data)
         {
           Rows.Add(new Row { FirstName = item.FirstName, LastName = item.LastName });
         }
       }
     });
  }
}

This looks easy and works just fine. There is a couple of things that you have to know. You have to call BindingOperations.EnableCollectionSynchronization with a lock object. Each time you want to change that collection you have to use that lock.

More information (a lot more) can be found https://docs.microsoft.com/en-us/dotnet/api/system.windows.data.bindingoperations.enablecollectionsynchronization?view=netframework-4.8

Leave a Reply

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