[DeveAlaPapa] Attendre une action sur un autre Thread sans .net 4… ni .net 3.5
Sans .net 4 ou 4.5, pas de Task, pas d’async/await, attendre un Thread ou le lancer doit donc se faire “à la papa”. Depuis l’arrivée de .net 4, je suis surpris de voir que bon nombre de développeurs ne savent plus faire ce genre d’opérations triviales.
Voici donc un petit code qui vous sera bien utile si vous n’avez que du .net 3.5 sous la main :
namespace MyLib
{
/// <summary>
/// Permet de gérer plus facilement des threads
/// </summary>
internal static class ThreadingHelperNet35
{
/// <summary>
/// Lance une action sur le Threadpool et fourni un AutoResetEvent pour attendre la fin de l'action
/// </summary>
/// <param name="action"></param>
/// <returns></returns>
internal static AutoResetEvent Queue(Action action)
{
AutoResetEvent e = new AutoResetEvent(false);
ThreadPool.QueueUserWorkItem(state =>
{
try
{
action();
}
finally
{
e.Set();
}
});
return e;
}
/// <summary>
/// Lance une action sur le Threadpool et attend la fin de l'action
/// </summary>
/// <param name="action"></param>
internal static void QueueAndWait(Action action)
{
AutoResetEvent e = Queue(action);
e.WaitOne();
}
}
}
La méthode Queue() utilise une mécanique simple, mais pourtant bien huilée :
- Un AutoResetEvent est instancié, il servira à attendre la fin de l’action.
- L’action est lancée via le ThreadPool
- À la fin de l’action, on fait un set sur l’AutoResetEvent ce qui met fin à l’attente.
La méthode QueueAndWait() utilise ce mécanisme.
Malheureusement, ce code utilise des particularités de .net 3.0 et 3.5 :
- System.Action
- Lambdas
Pour le faire fonctionner sur .net 2.0 il faudra donc passer par une adaptation du code :
- Déclarer un délégué pour l’action.
- Remplacer la lambda par une structure.
namespace MyLib
{
/// <summary>
/// Permet de gérer plus facilement des threads
/// </summary>
internal static class ThreadingHelperNet2
{
public delegate void Action();
/// <summary>
/// Lance une action sur le Threadpool et fourni un AutoResetEvent pour attendre la fin de l'action
/// </summary>
/// <param name="action"></param>
/// <returns></returns>
internal static AutoResetEvent Queue(Action action)
{
QueuedAction queuedAction = new QueuedAction(action);
ThreadPool.QueueUserWorkItem(queuedAction.Execute);
return queuedAction.Event;
}
/// <summary>
/// Lance une action sur le Threadpool et attend la fin de l'action
/// </summary>
/// <param name="action"></param>
internal static void QueueAndWait(Action action)
{
AutoResetEvent e = Queue(action);
e.WaitOne();
}
/// <summary>
/// Stucture permétant d'envelopper l'action à attendre
/// </summary>
private struct QueuedAction
{
private readonly Action _action;
private readonly AutoResetEvent _event;
/// <summary>
/// Constructeur
/// </summary>
/// <param name="action"></param>
internal QueuedAction(Action action)
{
_event = new AutoResetEvent(false);
_action = action;
}
/// <summary>
/// AutoResetEvent permétant l'attente
/// </summary>
internal AutoResetEvent Event { get { return _event; } }
/// <summary>
/// Executer l'action
/// </summary>
/// <param name="state"></param>
internal void Execute(Object state)
{
try
{
_action();
}
finally
{
_event.Set();
}
}
}
}
}
Et voilà du bon vieux Deve “à la papa”. Désolé pour la vue des plus jeunes, .net 2.0 c’était comme ça, ça piquait un peu (je n’ai plus ce qu’il faut sous la main pour tester, mais ce code doit aussi être compatible .net 1.0 et 1.1)
Dans un prochain article, on refait des HttpWebRequest à la main pour avoir une couche de service 100% portable (même pas mal)