Ceux qui on commencé à coder des activités se demandent certainement où je veux en venir avec un titre comme celui-ci. Mon objectif est relativement simple : faire la démonstration qu’un worklfow n’a pas besoin obligatoirement de passer par les bookmark pour interagir avec le monde extérieur de manière performante et sans provoquer de blocages.

Dans un workflow l’accès à des ressources externes non managées ou la réalisation d’opérations d’entrées sorties relativement lourdent peuvent devenir très handicapantes. Je parle là de dégradation des performances, consommation abusive de mémoire ou de processeur. Quand on rencontré ce genre de soucis et on met facilement la faute sur le dos WF sans penser que ce peut aussi provenir d’un mauvais code sur une activité custom.

Pour tout ce qui est activité lourde, ou bloquante il existe une activité de base idéale : l’AsyncCodeActivity. Afin de rendre la démonstration facilement compréhensible, j’ai codé une activité qui a pour objectif d’attendre que le réseau soit accessible à partir de l’hôte de workflows. Quand le réseau est disponible, l’activité retourne un Boolean vrai.

Voici son code. Il n’est pas très compliqué mais pour que sa lecture soit facilité je l’ai commenté le plus possible :

using System;
using System.Activities;
using System.Net.NetworkInformation;
using System.Threading;

namespace MyLib.Activities
{

    public sealed class WaitForNetWork : AsyncCodeActivity<Boolean>
    {
        // Handle pour l'attente du réseau
        private AutoResetEvent m_AutoResetEvent;

        /// <summary>
        /// Début d'execution de l'activité
        /// </summary>
        /// <param name="context"></param>
        /// <param name="callback"></param>
        /// <param name="state"></param>
        /// <returns></returns>
        protected override IAsyncResult BeginExecute(AsyncCodeActivityContext context, AsyncCallback callback, object state)
        {
            Action job = new Action(this.DoWork);
            context.UserState = job;
            return job.BeginInvoke(callback, state);
        }

        /// <summary>
        /// Fin d'execution de l'activité
        /// </summary>
        /// <param name="context"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        protected override Boolean EndExecute(AsyncCodeActivityContext context, IAsyncResult result)
        {
            Action job = context.UserState as Action;
            job.EndInvoke(result);

            // Retourner l'état du réseau nuiquement si l'activité n'a pas été annulée
            return context.IsCancellationRequested 
                ? false 
                : NetworkInterface.GetIsNetworkAvailable();
        }

        /// <summary>
        /// Réaction à la demande d'annulation de l'activité
        /// </summary>
        /// <param name="context"></param>
        protected override void Cancel(AsyncCodeActivityContext context)
        {
            // Libération du handle si on doit annuler l'activité
            if (this.m_AutoResetEvent != null)
            {
                this.m_AutoResetEvent.Set();
            }
            base.Cancel(context);
        }

        /// <summary>
        /// Travail à executer
        /// </summary>
        private void DoWork()
        {
            // Test si on doit vraiment attendre après le réseau
            if (!NetworkInterface.GetIsNetworkAvailable())
            {
                this.m_AutoResetEvent = new AutoResetEvent(false);

                // Habonnement aux changement de disponibilité du réseau
                NetworkChange.NetworkAvailabilityChanged += new NetworkAvailabilityChangedEventHandler(NetworkChange_NetworkAvailabilityChanged);

                // Attente de changements
                this.m_AutoResetEvent.WaitOne();
            }
        }

        /// <summary>
        /// Changement survenu sur la disponibilité du réseau
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void NetworkChange_NetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e)
        {
            // Test si le réseau est disponible
            if (e.IsAvailable)
            {
                this.m_AutoResetEvent.Set();
            }
        }
    }
}

La partie importante de ce code réside dans le code d’annulation car il est impératif de ne pas bloquer l’activité si le workflow a besoin d’annuler la séquence dans laquelle se trouve votre activité. Sans ce cancel, vous courrez au devant de gros soucis (blocage du workflow à un autre endroit alors qu’il bloque sur cette activité ci… et ce bug est difficile à trouver, très difficile).

En aillant une activité telle que celle-ci on peut imaginer un worklow comme celui-ci : deux branches dans un Pick. Une première branche attend indéfiniment le réseau et la seconde est chargée de s’assurer qu’au delà d’un certain délaie, le workflow poursuit sont exécution malgré tout.

wf4_asynce_network1

Si le réseau est trouvé dans la première branche avant la fin du délai de la seconde branche, on pourra avoir une série de message tel que ceux-ci :

wf4_asynce_network2

Et dans le cas contraire :

wf4_asynce_network3

Voici donc pour résume un bel exemple de ce qui peut être fait pour attendre une ressource et pour autant ne pas tout casser dans WF ;).