2010-07-06 16 views
13

Mon application doit se comporter légèrement différemment lorsqu'elle se charge si une instance est déjà en cours d'exécution.Détection si une autre instance de l'application est déjà en cours d'exécution

Je comprends comment utiliser un mutex pour empêcher le chargement d'instances supplémentaires, mais cela ne résout pas tout à fait mon problème.

Par exemple:

  • instance 1 charges, obtient le mutex.
  • L'instance 2 charge, ne peut pas obtenir le mutex, sait qu'il y a une autre instance. Jusqu'ici tout va bien.
  • L'instance 1 se ferme, libère le mutex.
  • Instance 3 charge, obtient le mutex, ne sait pas que l'instance 2 est toujours en cours d'exécution.

Des idées? Heureusement, il n'a pas besoin de gérer plusieurs comptes d'utilisateurs ou quelque chose comme ça.

(C#, application de bureau)

Edit: Pour clarifier, l'application n'a pas besoin d'être limité à une seule instance, il suffit de lancer une action de démarrage légèrement différent s'il y a une autre instance déjà en cours d'exécution. Plusieurs instances sont correctes (et attendues).

+0

dans le scénario que vous avez mentionné, quel est le comportement souhaité? Est-ce que l'instance 3 devrait faire ce que l'instance 1 faisait et l'instance 2 continuer à se comporter comme avant? (en supposant que vous ne voulez pas vous assurer d'une instance unique, puisque cela est résolu par le mutex, l'instance 2 finira par sortir quand même) –

+0

Dans ce scénario, l'instance 1 se comporte d'une manière, les instances 2 et 3 doivent utiliser le comportement alternatif. Si une instance est déjà en cours d'exécution, indépendamment du moment où elle a démarré ou de ce qui s'est passé depuis, une nouvelle instance se comportera légèrement, mais de manière non significative, différemment. La différence est seulement une action unique sur le chargement. – Andy

Répondre

12

Cela fera probablement ce que vous voulez. Il a la fonctionnalité supplémentaire intéressante de faire avancer l'instance en cours d'exécution.

EDIT: a mis à jour le code pour déterminer automatiquement le titre de l'application.

using System; 
using System.Diagnostics; 
using System.Linq; 
using System.Reflection; 
using System.Runtime.InteropServices; 

static void Main() 
{ 
    if (!EnsureSingleInstance()) 
    { 
     return; 
    } 

    //... 
} 

static bool EnsureSingleInstance() 
{ 
    Process currentProcess = Process.GetCurrentProcess(); 

    var runningProcess = (from process in Process.GetProcesses() 
          where 
          process.Id != currentProcess.Id && 
          process.ProcessName.Equals(
           currentProcess.ProcessName, 
           StringComparison.Ordinal) 
          select process).FirstOrDefault(); 

    if (runningProcess != null) 
    { 
     ShowWindow(runningProcess.MainWindowHandle, SW_SHOWMAXIMIZED); 
     SetForegroundWindow(runningProcess.MainWindowHandle); 

     return false; 
    } 

    return true; 
} 

[DllImport("user32.dll", EntryPoint = "SetForegroundWindow")] 
private static extern bool SetForegroundWindow(IntPtr hWnd); 

[DllImport("user32.dll")] 
private static extern Boolean ShowWindow(IntPtr hWnd, Int32 nCmdShow); 

private const int SW_SHOWMAXIMIZED = 3; 
+0

J'ajouterais que APPLICATION-TITLE est le nom de fichier exécutable sans l'extension (si je me souviens bien). –

+0

@sztomi: merci d'avoir déclenché la mémoire d'avoir du code qui traîne pour déterminer cela automatiquement. –

+0

Haha, c'est bien. Je pense que je l'ai codé en dur quand j'ai eu ce problème. C'est beaucoup mieux :) –

1

Essayez d'utiliser un Sémaphore au lieu d'un Mutex

2

Une autre approche consiste à détecter l'instance en cours d'exécution comme indiqué dans Scott Hanselman's blog

Son exemple active la première instance lorsque le deuxième essai. Cependant, il ne serait pas difficile d'obtenir que la seconde instance s'arrête juste si c'est ce que vous vouliez.

+1

Pourquoi personne n'utilisera le mécanisme existant dans [WindowsFormsApplicationBase] (http://msdn.microsoft.com/en-us/library/microsoft.visualbasic.applicationservices.windowsformsapplicationbase.aspx)? Juste parce que son espace de noms contient VisualBasic! Si les auteurs l'avaient mis dans un espace de noms Foo, il serait utilisé par beaucoup plus de gens. – Oliver

0

Pourriez-vous vérifier simplement GetLastError() après avoir créé le mutex avec CreateMutex()? Si elle renvoie ERROR_ALREADY_EXISTS, il existe une autre instance en cours d'exécution de votre application.

Selon http://msdn.microsoft.com/en-us/library/ms682411%28VS.85%29.aspx,

Si le mutex est un mutex nommé et l'objet existait avant cette fonction appel, la valeur de retour est une poignée pour l'objet existant, GetLastError renvoie ERROR_ALREADY_EXISTS, bInitialOwner est ignoré et le thread appelant ne possède pas la propriété . Toutefois, si l'appelant a droits d'accès limités, la fonction échouera avec ERROR_ACCESS_DENIED et l'appelant doit utiliser la fonction OpenMutex .

EDIT: viens juste de réaliser que c'était un C#/question net, désolé..

Dans .Net, utilisez le constructeur de Mutex qui retourne le drapeau createdNew, http://msdn.microsoft.com/en-us/library/bwe34f1k%28VS.80%29.aspx:

public Mutex (
    bool initiallyOwned, 
    string name, 
    out bool createdNew 
) 
0

une bonne approche consiste à utiliser la solution Sandor mais utiliser WMI pour obtenir la liste des procédés, décrits ici: C#: How to get the full path of running process? (La solution de Jeff). De cette façon, vous pouvez également vérifier si les autres instances en cours correspondent par chemin et ID de session de terminal distant:

static bool EnsureSingleInstance() 
    { 
     Process currentProcess = Process.GetCurrentProcess(); 

     var wmiQueryString = "SELECT ProcessId, ExecutablePath, CommandLine FROM Win32_Process"; 
     using (var searcher = new ManagementObjectSearcher(wmiQueryString)) 
     using (var results = searcher.Get()) 
     { 
      var query = from p in Process.GetProcesses() 
         join mo in results.Cast<ManagementObject>() 
         on p.Id equals (int)(uint)mo["ProcessId"] 
         select new 
         { 
          Process = p, 
          Path = (string)mo["ExecutablePath"], 
          CommandLine = (string)mo["CommandLine"], 
         }; 

      var runningProcess = (from process in query 
            where 
            process.Process.Id != currentProcess.Id && 
            process.Process.ProcessName.Equals(
             currentProcess.ProcessName, 
             StringComparison.Ordinal) && 
             process.Path == currentProcess.MainModule.FileName && 
             process.Process.SessionId == currentProcess.SessionId 
            select process).FirstOrDefault(); 

      return runningProcess == null; 
     } 
    } 
Questions connexes