Depuis le Framework .NET 4.5 il existe un mécanisme intégré de synchronisation des accès aux collections grâce à la méthode suivante : EnableCollectionSynchronization.

Autrement dit, il devient plus simple de charger une collection sur un thread autre que le thread de l’UI. L’intérêt principal réside dans le fait de pouvoir décorréler le chargement de l’UI du chargement des données.

Ainsi, l’UI ne fige pas et l’expérience utilisateur s’en trouve améliorée.

De prime abord, pour alimenter une collection sur un thread autre que celui de l’UI, on pourrait envisager quelque chose dans cet esprit :

/// <summary>
/// Charge les applications de manière asynchrone
/// </summary>
/// <returns>La <see cref="T:System.Threading.Tasks.Task"/>.</returns>
private async Task LoadApplicationsAsync()
{
    this.Items.Clear();
    //Chargement de la collection dans un nouveau thread
    await Task.Run(async () =>
        {
            var result = await this.serviceAgent.GetApplicationsAsync();

            foreach (var item in result)
            {
                this.Items.Add(item);
            }
        }
    );
}

Sauf que… la collection ne peut être mise à jour nativement hors du thread UI :

System.NotSupportedException: Ce type de CollectionView ne prend pas en charge les modifications de son SourceCollection

Il serait alors envisageable de contourner le problème en chargeant les données dans un thread, et, par ailleurs de mettre à jour la collection sur le thread de l'UI de cette façon:

/// <summary>
/// Charge les applications de manière asynchrone
/// </summary>
/// <returns>La <see cref="T:System.Threading.Tasks.Task"/>.</returns>
private async Task LoadApplicationsAsync()
{
    this.Items.Clear();
    //Chargement de la collection dans un nouveau thread
    await Task.Run(async () =>
        {
            var result = await this.serviceAgent.GetApplicationsAsync();

            foreach (var item in result)
            {
                System.Windows.Application.Current.Dispatcher.Invoke(() =>
                    {
                        this.Items.Add(item);
                    });
            }
        }
    );
}

 

Alors oui, ça « fonctionne »…

Mais outre la prouesse inesthétique, cette bascule entre thread est extrêmement coûteuse pour le processeur et cela a des répercussions au niveau des performances de l’application.

C’est là qu’intervient la méthode « EnableCollectionSynchronization ».

Il s’agit d’une méthode statique de la classe BindingOperations. L’appel peut se faire depuis le constructeur de la classe (ou tout autre endroit légitime sur le thread UI) :

BindingOperations.EnableCollectionSynchronization(this.Items, _lock);

« _lock » étant un objet lock qui va permettre la synchronisation des threads. Il peut être déclaré de cette façon :

private static object _lock = new object();

Dès lors, grâce à ce mécanisme de synchronisation des accès à la collection, il devient possible de mettre à jour la collection en dehors du thread principal (UI) de cette façon (identique au premier listing) :

/// <summary>
/// Charge les applications de manière asynchrone
/// </summary>
/// <returns>La <see cref="T:System.Threading.Tasks.Task"/>.</returns>
private async Task LoadApplicationsAsync()
{
    this.Items.Clear();
    //Chargement de la collection dans un nouveau thread
    await Task.Run(async () =>
        {
            var result = await this.serviceAgent.GetApplicationsAsync();

            foreach (var item in result)
            {
                this.Items.Add(item);
            }
        }
    );
}

 

Pour aller plus loin : la méthode « EnableCollectionSynchronization » peut également prendre en paramètre un callback appelé lors de l’opération de synchronisation. Pour des besoins spécifiques, il devient alors possible de gérer directement le processus de synchronisation.

Catégories : .Net

1 commentaire

Fabien T. · 2 mars 2015 à 16 h 17 min

Merci pour cette info, très pratique !

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *