Le multithreading en .NET

Bonjour à nos programmeurs .NET.

J’aurai une question pour ceux qui auraient de l’expérience dans la programmation multithreading en dotnet. Ayant une application .NET en cours d’optimisation, je rencontre quelques soucis (soucis relatifs) par rapport à son design.

Je suis déjà sensibilisé à rendre threadsafe les composants utilisés par mon application, mais je me demande jusqu’où je dois aller.

Exemple: j’ai une classe qui est “l’image” de la configuration de l’application (implémentation d’une section XML particulière et du SectionHandler qui va bien). L’instance de cette classe est mise en propriété statique d’une classe s’appelant ServiceConfiguration.

Les différents composants se basent sur les propriétés de cette classe, et des sous-classes enfants pour déterminer leur comportement.
Maintenant voici mon problème: mon application est hautement multithreading, effectuant pas mal d’accès à une base de données, plusieurs files d’attente MSMQ en écoute permanente, et du requêtage HTTP vers un serveur distant.

Ayant évité pas mal de problèmes de race condition avec l’accès à ces différentes ressources externes, dois-je également contrôler avec les SyncRoot et les lock qui vont bien les lectures sur ma propriété statique de ServiceConfiguration ?

J’ai déjà pas mal parcouru la doc MSDN, mais des avis d’expérimentés m’auraient bien plû.

Merci d’avance.

PS.: c’est plus anecdotique qu’utile, mais je travaille sur le .NET Framework 2.0 beta, le 2.0.50215.

Je vois que personne ne t’a répondu, donc je vais te donner mon avis, bien qu’il n’ai que peu de valeur, tout simplement car je n’ai jamais rencontré une telle problèmatique (quoique en ce moment je suis sur les CallContext, ThreadLocalStorage et ThreadStatic en asp.net :P)

A priori pour les variables statiques utilisées à bon escient (write once, read many), je ne vois pas en quoi un lock serait nécessaire pour la lecture. Tout dépend, je pense, de la manière dont tu initialises cette variable.

Sinon, tu as regardé du côté des champs volatile j’imagine ?

[quote name=‘Styx31’ date=’ 13 Jun 2005, 18:56’]Sinon, tu as regardé du côté des champs volatile j’imagine ?
[right][post=“367872”]<{POST_SNAPBACK}>[/post][/right][/quote]

Merci bien pour ta réponse.
Honte sur moi qui ne connaissait pas le mot clé volatile.

J’ai donc cherché un peu et je suis tombé là dessus :
http://msdn.labtech.epitech.net/Blogs/jayl…/08/05/704.aspx

Ca ne résoud pas complètement mon problème, étant déjà habitué à faire des lock correctement.
Je continue de chercher.

Non mais tout depend de l’acces que tu fais a tes static, lecture seule/ecriture, de la maniere dont tout est initialise avant ou apres le passage en mode multithreade (si c’est lazy init, alors il faut locker comme un salop et SURTOUT ne pas locker sur typeof(machin) parceque c’est ignomigneusement degueu et c’est le mal). Voila :stuck_out_tongue:

J’ai pas été assez clair, je l’avoue.
Donc ici mon problème (ou plutôt mon interrogation car mon programme fonctionne très bien sur monoproc, biproc, ou quadriproc) ne porte que sur l’accès à une de mes classes.

“Plutôt que des mots, démo” :

Je commence de zéro: le fichier de configuration.

[code]<?xml version="1.0" encoding="utf-8"?>

   
       
   
   
   
       
           
           
           
               alacon@cafzone.net
               alacon@gmail.com
           
       
   

[/code]

Les classes qui seront désérialisées de la section de configuration (version simplifiée) :

[code]using System;
using System.Xml.Serialization;

namespace PMLServiceLib.Configuration
{
   [Serializable()]
   [XmlRoot(Configuration.XmlElementName, IsNullable = false)]
   public class Configuration
   {
       public const string XmlElementName = “serviceConfiguration”;
       private Logger.Logger m_Logger;

       [XmlElement(IsNullable = false)]
       public Logger.Logger Logger
       {
           get { return m_Logger; }
           set { m_Logger = value; }
       }

       public Configuration()
       {
           m_Logger = new Logger.Logger();
       }
   }
}[/code]

Avec les petits frères en fichiers joints (version complète) :
[attachment=473:attachment]
[attachment=472:attachment]
[attachment=471:attachment]
[attachment=470:attachment]

Le SectionHandler (version simplifiée, tiré d’ici) qui va désérialiser ma section de configuration XML :

[code]using System;
using System.Xml;
using System.Xml.XPath;
using System.Xml.Serialization;
using System.Configuration;

namespace PMLServiceLib.Configuration
{
   public class SectionHandler : IConfigurationSectionHandler
   {
       public object Create(object parent, object configContext, XmlNode section)
       {
           XPathNavigator xpn = section.CreateNavigator();
           string tn = xpn.Evaluate(“string(@type)”).ToString();

           if (tn == string.Empty)
               throw new ConfigurationErrorsException(string.Format(“The type attribute is not present on the root node of the <{0}> configuration section.”, section.Name), section);

           Type t = Type.GetType(tn);

           if (t == null)
               throw new ConfigurationErrorsException(string.Format(“The type attribute ‘{0}’ specified in the root node of the the <{1}> configuration section is not a valid type.”, tn, section.Name), section);

           XmlSerializer xs = new XmlSerializer(t);
           XmlNodeReader xnr = new XmlNodeReader(section);

           try
           {
               return xs.Deserialize(xnr);
           }
           catch (Exception ex)
           {
               string s = ex.Message;
               Exception innerException = ex.InnerException;

               while (innerException != null)
               {
                   s += " " + innerException.Message;
                   innerException = innerException.InnerException;
               }

               throw new ConfigurationErrorsException(string.Format(“Unable to deserialize an object of type ‘{0}’ from the <{1}> configuration section: {2}”, tn, section.Name, s), ex, section);
           }
       }
   }
}[/code]

Puis la classe qui va appeller indirectement le processing de la section :

[code]using System;
using System.Configuration;

namespace PMLServiceLib
{
   public class ServiceConfiguration
   {
       private static Configuration.Configuration m_Root;

       public static Configuration.Configuration Root
       {
           get { return m_Root; }
       }

       static ServiceConfiguration()
       {
           m_Root = GetServiceConfiguration(Configuration.Configuration.XmlElementName);
       }

       private static Configuration.Configuration GetServiceConfiguration(string xpath)
       {
           Configuration.Configuration c = null;

           try
           {
               c = ConfigurationManager.GetSection(xpath) as Configuration.Configuration;
           }
           catch (ConfigurationErrorsException ex)
           {
               throw new ApplicationException(“Une erreur s’est produite lors du chargement du fichier de configuration.”, ex);
           }
           catch (Exception ex)
           {
               throw new ApplicationException(“Une erreur non gérée s’est produite lors du chargement du fichier de configuration.”, ex);
           }
           finally
           {
               if (c == null) throw new ApplicationException(“Le fichier de configuration est correct, mais il ne contient aucune configuration valide.”);
           }

           return c;
       }
   }
}[/code]

Enfin, dans le code du main, ou des threads (provenant d’un ThreadPool pour la plupart, ou sinon gérés également à la mimine avec new ThreadStart(), Thread.Start(), etc…) j’appelle en lecture ma configuration dans ce genre là:

if (ServiceConfiguration.Root.Logger.Mailer.Enabled) { &nbsp; &nbsp;// get beer }

Donc accès en lecture, puisque que seul le SectionHandler a instancié ma classe “Configuration”, et a mis à jour les propriétés via désérialisation XML.

J’espère que ça suffira pour comprendre mon problème.

En regardant juste du côté de ServiceConfiguration et de son constructeur statique, je ne pense pas qu’il y aura de problème : Le constructeur statique est appellé à la première utilisation d’une partie statique de la classe.

Donc à priori, au première accès le constructeur sera appellé, puis ensuite il n’y aura plus de problème pour l’accès à l’objet Root.

Les autres appels à la propriété doivent être mis en attente, le temps que la classe soit construite.