2010-07-18 3 views
2

Ma solution C# WinForm contient plusieurs projets dont un projet Admin contenant frmAdmin et un projet utilisateur contenant frmUser. Un troisième projet contient frmTimer qui a une minuterie qui lance périodiquement frmUser.Mutex non libéré

Je souhaite que frmTimer ne lance pas frmUser lorsque frmAdmin est ouvert.

J'utilise un mutex nommé pour indiquer à frmTimer si frmAdmin est ouvert; cependant, le mutex ne semble pas être libéré après la fermeture de frmAdmin.

Le mutex est créé en frmAdmin avec le code comme ceci:

public partial class frmAdmin : Form 
{ 
    Mutex m; 
    protected override void OnShown(EventArgs e) 
    { 
     base.OnShown(e); 
     m = new Mutex(true, "frmAdmin"); 
    } 
    protected override void OnClosed(EventArgs e) 
    { 
     base.OnClosed(e); 
     m.ReleaseMutex(); 
     MessageBox.Show("Debug 1 -- In the frmAdmin ONCLOSED Event."); //test code 
     Debug.WriteLine("Debug 1 -- In the frmAdmin ONCLOSED Event."); //test code 
    } 

    public frmAdmin(string strPassedFromLogin) 
    { 
     InitializeComponent(); 
     <<Code snipped>> 
      } 

    private void frmAdmin_FormClosing(object sender, FormClosingEventArgs e) 
    { 
     //Start _ Added 
     bool mutexSet = true; 
     try 
     { 
      Mutex.OpenExisting("frmAdmin"); 
      MessageBox.Show("Debug 2 -- In the frmAdmin FORMCLOSING Event."); //test code 
     } 
     catch (WaitHandleCannotBeOpenedException) 
     { 
      mutexSet = false; 
     } 
     if (mutexSet) 
     { 
      base.OnClosed(e); 
      m.ReleaseMutex(); 
     } 
     //End _ Added 

     Application.Exit(); 
    } 

    <<Code snipped>> 
} 

Dans un premier temps, je n'ai pas code mutex dans la méthode frmAdmin_FormClosing (la seule méthode contenait la ligne Application.Exit()). J'ai ajouté le code mutex dans une tentative de libérer le mutex, mais il n'est toujours pas publié.

Le mutex est utilisé dans frmTimer comme ceci:

private void tmTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) 
    { 
     bool adminIsOpen = true; 
     try 
     { 
      Mutex.OpenExisting("frmAdmin"); 
      MessageBox.Show("Debug 3 -- Mutex exists: frmAdmin IS open."); //test code 
     } 
     catch (WaitHandleCannotBeOpenedException) 
     { 
      adminIsOpen = false; 
      MessageBox.Show("Debug 4 -- Mutex doesn't exists: frmAdmin is NOT open."); //test code 
     } 

     if (adminIsOpen == false) 
     { 
      //frmAdmin is closed; go ahead and open frmUser. 
      <<Code snipped>> 
     } 
    } 

Quand je lance l'application, le messagebox avec le « Debug 4 » texte apparaît à chaque fois que les feux de la minuterie jusqu'à ce que j'ouvre frmAdmin (frmAdmin est lancé à partir frmLogin après la vérification du mot de passe), à ​​partir de ce moment-là, la boîte de message avec le texte 'Debug 3' apparaît chaque fois que la minuterie se déclenche, même après avoir quitté frmAdmin. Lorsque je quitte frmAdmin, je vois la boîte de message avec le texte 'Debug 2'. Je n'ai jamais vu le messagebox (ou un message de fenêtre de sortie) avec le texte 'Debug 1'.

Il semble que le mutex ne se libère pas après la fermeture de frmAdmin, ce qui empêche le lancement de frmUser.

Toute aide est appréciée.

Ceci est une question de suivi à this question.

MISE À JOUR

Voici mon code après avoir à travailler. Je l'ai eu à travailler en raison des réponses de Hans Passant et Chris Taylor et de Serhio de this post.

Le mutex est créé en frmAdmin avec le code comme ceci:

Mutex m; 
    protected override void OnShown(EventArgs e) 
    { 
     base.OnShown(e); 
     m = new Mutex(true, "frmAdmin"); 
    } 

    //This 'OnClosed' event is skipped when this application is terminated using only Exit(); therefore, call Close() before calling Exit(). 
    //The 'catch' code is added to insure the program keeps running in the event these exceptions occur. 
    protected override void OnClosed(EventArgs e) 
    { 
     if (m != null) 
     { 
      try 
      { 
       base.OnClosed(e); 
       m.ReleaseMutex(); 
       m.Close(); 
      } 
      catch (AbandonedMutexException) 
      { 
       //This catch is included to insure the program keeps running in the event this exception occurs. 
      } 
      catch (ApplicationException) 
      { 
       //This catch is included to insure the program keeps running in the event this exception occurs. 
      } 
      catch (SynchronizationLockException) 
      { 
       //This catch is included to insure the program keeps running in the event this exception occurs. 
      } 
     } 
    } 

Le mutex est utilisé dans frmTimer comme ceci:

private void tmTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) 
{ 
    bool adminIsOpen = false; 
    Mutex _muty = null; 
    try 
    { 
     //If the named mutex does not exist then OpenExisting will throw the 'WaitHandleCannotBeOpenedException', 
     //otherwise the mutex exists and Admin is open. 
     _muty = Mutex.OpenExisting("frmAdmin"); 
     adminIsOpen = true; 
     _muty.Close(); 
    } 
    catch (WaitHandleCannotBeOpenedException) 
    { 
     //This catch is thrown when Admin is not opened (keep 'adminIsOpen = false'). Do not delete this catch. 
    } 
    catch (AbandonedMutexException) 
    { 
     //This catch is included to insure the program keeps running in the event this exception occurs. 
    } 

    if (adminIsOpen == false) 
    { 
     //frmAdmin is closed; go ahead and open frmUser. 
     <<Code snipped>> 
    } 
} 

Répondre

3

Le problème est dans le gestionnaire d'événements Elapsed, il vérifie si le mutex existe avec Mutex.OpenExisting(). Bien sûr qu'il existe. Vous n'êtes pas en train de vérifier si c'est signalé. Cela prend l'appel de sa méthode WaitOne (0).

Méfiez-vous également que la création d'un formulaire dans un événement Timer.Elapsed est tout à fait inapproprié. Cet événement exécute un thread de pool de threads, il n'est pas du tout approprié pour agir comme un thread de l'interface utilisateur. Il a le mauvais état COM ([STAThread] et Thread.SetApartmentState), une propriété que vous ne pouvez pas modifier sur un thread threadpool. Utilisez un Form.Timer régulière à la place afin que le formulaire soit créé sur le thread d'interface utilisateur du programme. Edit: méfiez-vous également de la course inévitable, la minuterie pourrait créer le formulaire utilisateur une microseconde avant ferme le formulaire Admin. En d'autres termes, vous aurez un formulaire Utilisateur sans formulaire Admin, la seule condition que vous avez écrit ce code pour empêcher. Est-ce approprié? Essayer des formes dans différents processus de s'influencer est une mauvaise idée ...

+0

Bonjour Hans, Merci d'avoir répondu. En guise de suivi, j'ai un autre mutex qui est utilisé pour s'assurer que l'application n'est lancée qu'une seule fois. Est-ce que cela affectera mon utilisation de WaitOnce (0)? Comment WaitOne (0) sait-il quel mutex rechercher? – Frederick

+0

Ne pas ignorer la valeur de retour de Mutex.OpenExisting(), c'est le mutex que vous recherchez. En fait, la signalisation du mutex n'est pas nécessaire, juste l'existence suffit. Veillez à appeler la méthode Close(). Vérifiez ce fil: http://stackoverflow.com/questions/1904519/how-to-call-win32-createmutex-from-net –

+0

pour suivre votre avertissement sur l'utilisation d'un événement Timer.Elapsed pour créer un nouveau formulaire: Si ma réponse est tout simplement changer: private void tmTimer_Elapsed (expéditeur d'objet, System.Timers.ElapsedEventArgs e) { à quelque chose comme ceci: private void timer1_Tick (expéditeur d'objet, System.EventArgs e) { Et puis ajoutez également ces deux lignes: private System.Windows.Form.Timer timer1; this.timer1.Tick + = new System.EventHandler (this.timer1_Tick); Si la réponse est complexe, je peux la poster comme une nouvelle question. – Frederick

3

Le problème est qu'une fois que vous exécutez l'application d'administration de la Mutex existe et l'OpenExisting réussit. Libérer un Mutex ne détruit pas l'objet Kernel, il libère juste le maintien sur le mutex afin que les autres threads en attente puissent s'exécuter. Par conséquent, les appels Mutex.OpenExisting suspects ouvrent le mutex avec succès.

Vous voulez probablement utiliser Mutex.WaitOne(TimeSpan) si vous ouvrez la mutex et si WaitOne retourne false alors vous savez que vous vous ne pouviez pas acquérir le mutex si l'application Admin détiennent encore le mutex.

+0

Merci Chris, votre réponse et Hans sont d'accord. Je vais l'implémenter. Merci encore. – Frederick

+0

Bonjour Chris, si tu es toujours là, peux-tu me dire comment détruire l'objet Kernel du mutex? J'ai essayé mutex.Close(), mais cela ne semble pas le faire. – Frederick

+1

@Frederick, l'objet kernel ne sera fermé que lorsque toutes les références seront libérées. Ainsi, dans votre exemple, votre application d'administration crée une référence et lorsque OpenExisting ouvre le mutex, c'est une autre référence, les deux devront donc se fermer avant que l'objet du noyau ne soit détruit. –