2010-08-17 10 views
0

J'utilise Visual Studio 2010 pour écrire une application GUI C#/.NET simple, dans laquelle j'utilise une classe Logger pour écrire des informations de suivi/débogage dans un seul fichier à partir de tous les différents classes du projet. (Voir le code source ci-dessous.)Plusieurs threads accédant à l'objet singleton dans VS2010

Le constructeur de chaque classe écrit une entrée dans le journal lorsque l'un de ses types d'objets est instancié. L'une de ces classes est un composant de contrôleur GUI personnalisé (classe FileAttributesCtl), qui est contenu dans un couple des formulaires GUI utilisés par le programme.

Le problème que j'ai est que deux fichiers journaux sont créés, environ 200 msec. Le premier fichier journal contient (uniquement) un message indiquant qu'un objet FileAttributesCtl a été construit et le second contient tous les autres messages écrits dans le flux de sortie du fichier journal (supposé) partagé. Donc, chaque fois que j'exécute mon code de projet, j'obtiens deux fichiers journaux.

étrange encore, chaque fois que je reconstruire mon projet (F6), un fichier journal est créé pour l'objet FileAttributesCtl, ce qui indique qu'un objet de contrôle de ce type est en fait instancié au cours du processus de construction.

Cela a apparemment quelque chose à voir avec le filetage. Si le fichier journal n'est pas nommé de manière unique (si je n'ajoute pas de chaîne de date/heure unique au nom de fichier), j'obtiens une exception, indiquant que plusieurs processus (qui est en fait le processus VS2010) utilisent actuellement le fichier. Donc, ma question est: Comment puis-je obtenir l'objet singleton pour être un seul objet?

Une question secondaire est: pourquoi le VS2010 agit-il de cette manière?

//---------------------------------------- 
// Logger.cs 
class Logger 
{ 
    // Singleton object 
    private static Logger s_logger = 
     new Logger("C:/Temp/foo.log"); 

    public static Logger Log 
    { 
     get { return s_logger; } 
    } 

    private TextWriter m_out; 

    private Logger(string fname) 
    { 
     // Add a date/time suffix to the filename 
     fname = ...; 

     // Open/create the logging output file 
     m_out = new StreamWriter(
      new FileStream(fname, FileMode.Create, FileAccess.Write, 
       FileShare.Read)); 
     m_out.WriteLine(DateTime.Now.ToString(
      "'$ 'yyyy-MM-dd' 'HH:mm:ss.fff")); 
    } 

    ... 
} 

//---------------------------------------- 
// FileAttributesCtl.cs 
public partial class FileAttributesCtl: UserControl 
{ 
    private Logger m_log = Logger.Log; 

    public FileAttributesCtl() 
    { 
     m_log.WriteLine("FileAttributesCtl()"); //Written to first logfile 
     InitializeComponent(); 
    } 

    ... 
} 

//---------------------------------------- 
// FileCopyForm.cs 
public partial class FileCopyForm: Form 
{ 
    private Logger m_log = Logger.Log; 

    public FileCopyForm() 
    { 
     // Setup 
     m_log.WriteLine("FileCopyForm()");  //Written to second logfile 

     // Initialize the GUI form 
     m_log.WriteLine("FileCopyGui.InitializeComponent()"); 
     InitializeComponent(); 
     ... 
    } 

    ... 
} 

Note: Ceci est très similaire à une question de Décembre 2009:
Access to singleton object from another thread
mais il n'a pas les réponses à ma question.

Mise à jour

Une enquête plus approfondie montre que le VS2010 est instancié en effet le composant personnalisé lors de la construction, probablement pour qu'il puisse le rendre dans la fenêtre Designer.

En outre, il existe en effet deux threads distincts appelant le constructeur Logger (chacun ayant un ManagedThreadID différent). L'utilisation d'un initialiseur de classe statique pour construire l'objet singleton ne fonctionne pas; J'ai toujours deux fichiers journaux.

Résolution

Un examen plus approfondi, je remarque que le contrôle personnalisé est de s'instancié deux fois, et ce qui est montré dans les deux fichiers journaux.

Par conséquent, je pense que le problème est entièrement dû au fait que VS instancie l'objet de contrôle personnalisé avant d'exécuter le programme qui entraîne la création du premier fichier journal. Le deuxième fichier journal est ensuite créé après que le programme a démarré l'exécution normale.

Ainsi, le premier fichier journal est simplement un effet secondaire du processus de construction et n'a rien à voir avec l'exécution de plusieurs threads pendant l'exécution normale du programme.

La solution évidente consiste à supprimer tout le code d'effet secondaire du fichier journal des constructeurs de composants. Ou tout simplement ignorer complètement le premier fichier journal.

+0

Vous devriez mieux télécharger log4net pour faire le travail :) – Zebi

+0

Est-ce que quelqu'un sait si 'log4net' est immunisé à ce problème? –

+0

Je ne sais pas à propos de immunitaire, mais de leur FAQ ... Est-ce que log4net thread-safe? Oui, log4net est thread-safe. –

Répondre

1

Il se peut très bien que Visual Studio construise votre composant d'interface utilisateur (à afficher dans le concepteur) et dans le processus, votre constructeur est appelé, ce qui explique pourquoi vous voyez ce fichier journal pendant le processus de construction.

+0

Cela semble raisonnable, puisque quand j'avais précédemment une exception (parce que le nom de fichier était toujours le même, sans un suffixe unique), la fenêtre du concepteur VS affichait une erreur, étant incapable de redessiner le formulaire. Cela semble être une façon immensément stupide de le faire, car la création du composant nu pourrait provoquer des effets secondaires, dont certains pourraient être nuisibles si le programme principal n'a pas été créé. –

+0

@Loadmaster Le concepteur est là pour vous donner un aperçu du comportement du formulaire lorsqu'il est instancié dans votre application. La façon la plus précise de le faire est d'utiliser le code que vous avez spécifié pour instancier - quoi de mieux que de vous montrer le vrai? – Jake

+1

@Loadmaster Le constructeur n'est pas l'endroit approprié pour effectuer des opérations provoquant des effets secondaires. C'est l'événement 'Load' du formulaire. –

1

données statiques + fils = trouble

Vous devez synchroniser l'accès au singleton (et l'initialisation du singleton).

Un constructeur statique peut aider

class Logger 
{ 
    private static Logger 
    static Logger() 
    { 
     s_logger = new Logger("C:/Temp/foo.log"); 
    } 

    // ... 

ou mieux encore utiliser une bibliothèque de journalisation (log4net) qui gère tout ce genre de choses pour vous.

En ce qui concerne les constructions VS provoquant la création d'un journal, je ne suis pas surpris. Il instancie probablement les formulaires pour découvrir des informations sur vos formulaires via la réflexion.

mise à jour par commentaires

@LoadMaster « La initialiseur de classe statique ne pas travail. J'ajouté plus d'informations dans le fichier journal sortie pour inclure le de thread courant ManagedThreadID, et bien sûr, il sont deux ID de thread différents créant les deux fichiers journaux. "

C'est étrange. Par MSDN

Le constructeur statique pour une classe exécute au plus une fois dans un domaine d'application donné. L'exécution d'un constructeur statique est déclenchée par la première des événements suivants se produise au sein d'un domaine d'application:

  • Une instance de la classe est créée. L'un des membres statiques de la classe est référencé.

Votre fils doit avoir déplacé AppDomains ou il y a un code manquant dans vos extraits.

+0

Je n'avais pas besoin d'une classe de journalisation complexe, c'est pourquoi j'ai construit ma propre classe très simple. J'ai l'intention de l'enlever du code final de toute façon. –

+1

Et par conséquent, vous finirez par réimplémenter la moitié :) –

+0

L'initialiseur de classe statique ne fonctionne pas. J'ai ajouté plus d'informations à la sortie du fichier journal pour inclure le 'ManagedThreadID' du thread courant, et bien sûr, il y a deux identifiants de threads différents créant les deux fichiers journaux. –