Gestion des Priorités dans les Applications WinForms

Contrairement à WPF qui dispose de l'énumération DispatcherPriority, WinForms ne propose pas de système de priorité intégré pour l'exécution des opérations sur le fil d'interface graphique. Les méthodes Control.Invoke et Control.BeginInvoke permettent de transférer des délégués vers le thread UI, mais toutes les opérations soumises s'exécutent dans leur ordre d'arrivée.

Mécanismes de base pour le thread UI

WinForms offre deux approches fondamentales pour mettre à jour l'interface depuis un thread secondaire :

// Mise à jour de l'interface depuis un thread d'arrière-plan
private void RafraichirDepuisThreadSecondaire()
{
    Task.Factory.StartNew(() =>
    {
        // Simulation d'un traitement long
        Thread.Sleep(800);

        // Approche asynchrone : ne bloque pas le thread appelant
        monLabel.BeginInvoke(new Action(() =>
        {
            monLabel.Text = "Traitement terminé";
        }));

        // Approche synchrone : attend la fin de l'exécution sur le thread UI
        // monLabel.Invoke(new Action(() =>
        // {
        //     monLabel.Text = "Traitement terminé";
        // }));
    });
}

Pour vérifier si un appel inter-thread est nécessaire, on utilise la propriété InvokeRequired :

private void MettreAJourTexte(string contenu)
{
    if (monChampTexte.InvokeRequired)
    {
        monChampTexte.BeginInvoke(new Action(() => MettreAJourTexte(contenu)));
        return;
    }

    monChampTexte.Text = contenu;
}

Simulatino de comportements avec priorités

1. Utilisation de l'événement Application.Idle

L'événement Application.Idle se déclenche lorsque la file de messages Windows est vide. C'est l'androit idéal pour exécuter des tâches peu urgentes :

public partial class FenetrePrincipale : Form
{
    private readonly Queue<Action> _tachesEnAttente = new Queue<Action>();

    public FenetrePrincipale()
    {
        InitializeComponent();
        Application.Idle += QuandApplicationInoccuppee;
    }

    private void QuandApplicationInoccuppee(object sender, EventArgs e)
    {
        while (_tachesEnAttente.Count > 0)
        {
            var tache = _tachesEnAttente.Dequeue();
            tache();
        }
    }

    public void PlanifierTacheSecondaire(Action tache)
    {
        _tachesEnAttente.Enqueue(tache);
    }
}

2. Différer l'exécution avec un minuteur

On peut utiliser System.Windows.Forms.Timer pour retarder l'exécution de certaines opérations :

public partial class FenetrePrincipale : Form
{
    private System.Windows.Forms.Timer _minuteurDiffere;

    public FenetrePrincipale()
    {
        InitializeComponent();
        _minuteurDiffere = new System.Windows.Forms.Timer { Interval = 150 };
        _minuteurDiffere.Tick += QuandLeMinuteurDeclenche;
    }

    private void QuandLeMinuteurDeclenche(object sender, EventArgs e)
    {
        _minuteurDiffere.Stop();
        TraiterDonneesSecondaires();
    }

    public void ReporterTraitement()
    {
        _minuteurDiffere.Start();
    }
}

3. File de priorités personnalisée

Pour une gestion plus fine, on peut construire une extension qui hiérarchise les opérations :

public static class ExtensionsControle
{
    public static void ExecuterAvecPriorite(
        this Control controle,
        Action action,
        NiveauPriorite niveau = NiveauPriorite.Standard)
    {
        if (!controle.InvokeRequired)
        {
            action();
            return;
        }

        switch (niveau)
        {
            case NiveauPriorite.Immediate:
                controle.Invoke(action);
                break;

            case NiveauPriorite.Standard:
                controle.BeginInvoke(action);
                break;

            case NiveauPriorite.Reportee:
                Task.Delay(200).ContinueWith(_ =>
                {
                    controle.BeginInvoke(action);
                });
                break;

            case NiveauPriorite.Inactive:
                var handler = default(EventHandler);
                handler = (_, __) =>
                {
                    Application.Idle -= handler;
                    controle.BeginInvoke(action);
                };
                Application.Idle += handler;
                break;
        }
    }
}

public enum NiveauPriorite
{
    Immediate,  // Blocage jusqu'à exécution
    Standard,   // Envoi asynchrone classique
    Reportee,   // Exécution différée
    Inactive    // Uniquement quand l'application est inoccupée
}

Utilisation :

monLabel.ExecuterAvecPriorite(
    () => monLabel.Text = "Mise à jour différée",
    NiveauPriorite.Reportee
);

Bonnes pratiques

Privilégier BeginInvoke à Invoke

// ✅ Recommandé : évite les interblocages
private void MiseAJourSure()
{
    if (this.InvokeRequired)
    {
        this.BeginInvoke(new Action(() => MiseAJourSure()));
        return;
    }

    // Code de mise à jour UI
}

// ⚠️ Risqué : peut provoquer un interblocage
private void MiseAJourRisquee()
{
    if (this.InvokeRequired)
    {
        this.Invoke(new Action(() => MiseAJourRisquee()));
        return;
    }
}

Utiliser async/await avec SynchronizationContext

public partial class FenetrePrincipale : Form
{
    private readonly SynchronizationContext _contexteUI;

    public FenetrePrincipale()
    {
        InitializeComponent();
        _contexteUI = SynchronizationContext.Current;
    }

    private void LancerTraitement()
    {
        Task.Run(async () =>
        {
            var resultat = await RecupererDonneesAsync();

            _contexteUI.Post(_ =>
            {
                grilleDonnees.DataSource = resultat;
            }, null);
        });
    }
}

Comparaison WinForms vs WPF

Aspect WPF WinForms
Mécanisme de dispatch Dispatcher Control.Invoke/BeginInvoke
Système de priorité Oui (DispatcherPriority) Non (traitement FIFO)
Vérification thread CheckAccess() InvokeRequired
Traitement à l'arrêt Priorité ApplicationIdle Événement Application.Idle

WinForms adopte un modèle de fil de messages plus direct. Pour la majorité des applications, ce fonctionnement séquentiel suffit amplement. Seules les situations nécessitant un contrôle granulaire de l'ordonnencement justifient la mise en place d'un système de priorités sur mesure.

Étiquettes: WinForms C# Invoke BeginInvoke Application.Idle

Publié le 1 juillet à 08h39