Gérer la synchronisation de ses agents avec l’application

Gérer la synchronisation de ses agents avec l’application

Il existe de nombreux scénarios où un développeur ne souhaite pas lancer un background agent (périodique par exemple) lorsque son application est lancée, ceci pour différentes raisons:

  • l’application en foreground réalise un traitement équivalent à celui dans le background agent, voire + complet
  • résoudre des problématiques d’accès de fichiers
  • obiwan kenobi

C’est une question que je vois régulièrement sur les forums, malheureusement, aucune solution n’est indiquée si ce n’est quelques idées et propositions, mais rien de concret.

Donc corrigeons cela pour la nouvelle année !

La solution idéale ?

C’est un peu un rêve… étant donné que winrt gère les conditions sur les background agents:


builder.AddCondition(new SystemCondition(SystemConditionType.InternetAvailable));

Il ferait sens d’avoir un équivalent pour qu’un agent ne se lance que si l’application n’est pas déjà lancée, par exemple:


builder.AddCondition(new SystemCondition(SystemConditionType.ApplicationNotRunning));

Malheureusement ce genre de condition n’est toujours pas disponible ce qui est plutôt regrettable… Nous allons donc devoir bricoler une solution maison car soyons honnête, la solution se rapproche vraiment du bricolage, mais faute de mieux…

Le secret: les mutex nommés

Pour résoudre notre problématique, nous allons utiliser ce qu’on appelle les Mutex nommés, ces mutex (ou Semaphore(1) pour ceux qui sont plus familier avec cette notion) ont la particularité de porter un nom/identifiant et d’être multi-process.

Attention toutefois, les applications “Metro” étant sandboxées, ce mutex se limite au scope de l’application, aucun risque donc d’avoir des soucis de synchronisation si 2 applications utilisent le même nom/id.

mutex

Implémentation

Nous allons donc commencer par créer deux variables, un mutex et un booléen:


private static Mutex _agentMutex=new Mutex(false,"SyncAgentsMutex");
private static bool _applicationRunning;

Ce booléen est un peu redondant avec le Mutex, mais cela est fait exprès, il va permettre de stocker l’état du Mutex, la classe .Net ne nous permettant pas d’avoir accès à cette information directement.

Nous allons maintenant gérer la prise du Mutex afin d’indiquer que l’application est en cours d’exécution


private static void ApplicationIsLaunched()
{
if (_applicationRunning)
return;
lock (_agentMutex)
{
if (_applicationRunning)
return;
_agentMutex.WaitOne();
_applicationRunning = true;
}
}

l’implémentation est classique, un lock pour être sûr de ne pas appeler deux fois WaitOnce (ce qui pourrait bloquer l’application au second appel) et une double vérification du booléen à l’extérieur et à l’intérieur du lock, schéma plutôt classique.

Occupons nous maintenant de la fonction de libération du Mutex. Contrairement aux cas précédent, j’ai volontairement choisi de ne pas faire une vérification avant le lock, je veux être sûr et certain qu’un ApplicationIsLaunched n’est pas en cours, ce qui pourrait nous causer quelques problèmes de synchronisation.


private static void ApplicationIsSuspended()
{
lock (_agentMutex)
{
if (!_applicationRunning)
return;
_agentMutex.ReleaseMutex();
_applicationRunning = false;
}
}

Enfin, nous allons créer une fonction pour vérifier l’état de l’application


public static bool IsApplicationLaunched()
{
lock (_agentMutex)
{
if (_agentMutex.WaitOne(1))
{
_agentMutex.ReleaseMutex();
return false;
}
return true;
}
}

WaitOne(1) va nous permettre de tester l’état du Mutex, pour résumer, on va essayer d’acquérir le Mutex avec un timeout d’1 milliseconde (soit presque rien), si on réussi à le récupérer, cela signifie que l’application n’est pas en cours d’exécution, on demande alors à relâcher le mutex aussitôt.

Précision important, je n’ai pas utiliser le booléen _applicationRunning. En effet, mon application et mes agents sont dans des processus différent, autant le Mutex est synchronisé (car nommé), autant le booléen est propre à chaque processus. Comme évoqué précédemment, le booléen est là pour sécuriser le Mutex côté application, et uniquement de ce côté.

Dernière étape, deux fonctions pour enregistrer l’application et gérer automatiquement l’état de celle-ci

 

        public static void Init(Application app)
        {
            app.Resuming += App_Resuming;
            app.Suspending += App_Suspending;
            ApplicationIsLaunched();
        }

        private static void App_Suspending(object sender, Windows.ApplicationModel.SuspendingEventArgs e)
        {
            ApplicationIsSuspended();
        }

        private static void App_Resuming(object sender, object e)
        {
            ApplicationIsLaunched();
        }

Comment l’utiliser ?

Côté application

Dans le constructeur de votre application (App.xaml.cs), appelez


AgentSync.Init(this);

le plus tôt possible (première ligne si possible) et c’est tout.

Côté agent

Dès la première ligne de votre agent, dans la méthode Run(…), appelez la méthode suivante :


if(AgentSync.IsApplicationLaunched())
return;

Cela évitera tout traitement si votre application est en cours d’utilisation.

Code complet

using System.Threading;
using Windows.UI.Xaml;

namespace Sixtin.Services.Utils
{
    public class AgentSync
    {
        private static Mutex _agentMutex=new Mutex(false,"SyncAgentsMutex");
        private static bool _applicationRunning;

        public static void Init(Application app)
        {
            app.Resuming += App_Resuming;
            app.Suspending += App_Suspending;
            ApplicationIsLaunched();
        }

        private static void App_Suspending(object sender, Windows.ApplicationModel.SuspendingEventArgs e)
        {
            ApplicationIsSuspended();
        }

        private static void App_Resuming(object sender, object e)
        {
            ApplicationIsLaunched();
        }

            private static void ApplicationIsLaunched()
            {
                if (_applicationRunning)
                    return;
                lock (_agentMutex)
                {
                    if (_applicationRunning)
                        return;
                    _agentMutex.WaitOne();
                    _applicationRunning = true;
                }
            }

        private static void ApplicationIsSuspended()
        {
            lock (_agentMutex)
            {
                if (!_applicationRunning)
                    return;
                _agentMutex.ReleaseMutex();
                _applicationRunning = false;
            }
        }

        public static bool IsApplicationLaunched()
        {
            lock (_agentMutex)
            {
                if (_agentMutex.WaitOne(1))
                {
                    _agentMutex.ReleaseMutex();
                    return false;
                }
                return true;
            }
        }
    }
}

Comments are closed.