PriorityHandler : Une implémentation de ISynchronizeInvoke

Brouillon

À venir

0 commentaire

Dans un précèdent article je vous avais présenté le Timer asservi : l’EnslavedTimer.

Ce Timer asservi permet d’avoir un temps d’exécution constant dans la durée, mais il pourrait y avoir un problème si jamais le Thread d’exécution de l’évènement restait bloqué trop longtemps.

C'est notamment le cas si jamais le temps d’exécution de l’évènement dépasse les 500ms, en reprenant l’exemple du précédent article.

Analyse

 

Commençons par analyser le Thread d’exécution de l’évènement de l’EnslavedTimer.

Le code ci-dessous permet d’afficher différentes informations à propos du Thread d’exécution.

private static void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
    Console.WriteLine("Timer ThreadId : " + System.Threading.Thread.CurrentThread.ManagedThreadId);
    Console.WriteLine("Timer Thread priority : " + System.Threading.Thread.CurrentThread.Priority.ToString());
}

La sortie console nous donne :

image

 

On peut voir que l’évènement du Timer est exécuté dans un autre Thread et que sa priorité est Normal.

La priorité de ce Thread pourrait être un problème, en effet, en gardant notre exemple de serveur de streaming audio, si jamais notre Thread restait bloqué à cause d’un autre on risquerait de perdre des données.

 

Solution

 

Pour résoudre ce problème il suffit de modifier la priorité du Thread d’exécution du Timer et de la mettre en priorité élevé.

Pour cela la méthode la plus propre est d’utiliser la propriété SynchronizingObject du System.Timers.Timer.

 

Implémentation

 

public class PriorityHandler : ISynchronizeInvoke
{
    private readonly object _sync;

    public PriorityHandler()
    {
        _sync = new object();
    }

    public bool InvokeRequired
    {
        get
        {
            return true;
        }
    }
    
    public IAsyncResult BeginInvoke(Delegate method, object[] args)
    {
        var result = new SimpleAsyncResult();

        Thread thread = new Thread(new ThreadStart(delegate ()
        {
            result.AsyncWaitHandle = new ManualResetEvent(false);
            result.AsyncState = Invoke(method, args);
            result.IsCompleted = true;
            ((ManualResetEvent)result.AsyncWaitHandle).Set();
        }));
        thread.Priority = ThreadPriority.Highest;
        thread.IsBackground = true;
        thread.Start();

        return result;
    }

    public object EndInvoke(IAsyncResult result)
    {
        if (!result.IsCompleted)
        {
            result.AsyncWaitHandle.WaitOne();
        }

        return result.AsyncState;
    }

    public object Invoke(Delegate method, object[] args)
    {
        lock (_sync)
        {
            return method.DynamicInvoke(args);
        }
    }
}

La propriété InvokeRequired permet de spécifier si le Delegate de l’évènement sera appelé via l’objet PriorityHandler ou si le Timer le gèrera lui-même.

 

Au niveau du Timer il suffit de définir la propriété SynchronizingObject  sur une instance de la classe PriorityHandler.

_timer = new Timer();
_timer.Elapsed += Timer_Elapsed;
_timer.Interval = interval;
_timer.SynchronizingObject = new PriorityHandler();

 

Résultat

En reprenant le même exemple que précédemment.

 

image

 

La priorité du Thread est maintenant en Highest.

L’évènement du Timer sera exécuté dans un Thread qui aura une priorité élevée et qui ne restera pas bloqué par l’exécution d’autres Thread.

Xavier ARDISSON

Diplomé de l'école polytechnique universitaire de Paris VI (UPMC)

Profil de l'auteur