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 :
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.
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.