La classe Mutex est très mal comprise, et les mutex globaux encore plus.
Quel est le modèle correct et sûr à utiliser lors de la création de mutex globaux?
Celui qui fonctionnera
Je veux m'assurer que c'est là-bas, car il est si difficile de bien faire les choses:
using System.Runtime.InteropServices; //GuidAttribute
using System.Reflection; //Assembly
using System.Threading; //Mutex
using System.Security.AccessControl; //MutexAccessRule
using System.Security.Principal; //SecurityIdentifier
static void Main(string[] args)
{
// get application GUID as defined in AssemblyInfo.cs
string appGuid =
((GuidAttribute)Assembly.GetExecutingAssembly().
GetCustomAttributes(typeof(GuidAttribute), false).
GetValue(0)).Value.ToString();
// unique id for global mutex - Global prefix means it is global to the machine
string mutexId = string.Format( "Global\\{{{0}}}", appGuid );
// Need a place to store a return value in Mutex() constructor call
bool createdNew;
// edited by Jeremy Wiebe to add example of setting up security for multi-user usage
// edited by 'Marc' to work also on localized systems (don't use just "Everyone")
var allowEveryoneRule =
new MutexAccessRule( new SecurityIdentifier( WellKnownSidType.WorldSid
, null)
, MutexRights.FullControl
, AccessControlType.Allow
);
var securitySettings = new MutexSecurity();
securitySettings.AddAccessRule(allowEveryoneRule);
// edited by MasonGZhwiti to prevent race condition on security settings via VanNguyen
using (var mutex = new Mutex(false, mutexId, out createdNew, securitySettings))
{
// edited by acidzombie24
var hasHandle = false;
try
{
try
{
// note, you may want to time out here instead of waiting forever
// edited by acidzombie24
// mutex.WaitOne(Timeout.Infinite, false);
hasHandle = mutex.WaitOne(5000, false);
if (hasHandle == false)
throw new TimeoutException("Timeout waiting for exclusive access");
}
catch (AbandonedMutexException)
{
// Log the fact that the mutex was abandoned in another process,
// it will still get acquired
hasHandle = true;
}
// Perform your work here.
}
finally
{
// edited by acidzombie24, added if statement
if(hasHandle)
mutex.ReleaseMutex();
}
}
}
En utilisant la réponse acceptée, je crée une classe d'assistance afin que vous puissiez l'utiliser de la même manière que vous utiliseriez l'instruction Lock. Je pensais juste partager.
Utilisation:
using (new SingleGlobalInstance(1000)) //1000ms timeout on global lock
{
//Only 1 of these runs at a time
RunSomeStuff();
}
Et la classe d'assistance:
class SingleGlobalInstance : IDisposable
{
//edit by user "jitbit" - renamed private fields to "_"
public bool _hasHandle = false;
Mutex _mutex;
private void InitMutex()
{
string appGuid = ((GuidAttribute)Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(GuidAttribute), false).GetValue(0)).Value;
string mutexId = string.Format("Global\\{{{0}}}", appGuid);
_mutex = new Mutex(false, mutexId);
var allowEveryoneRule = new MutexAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), MutexRights.FullControl, AccessControlType.Allow);
var securitySettings = new MutexSecurity();
securitySettings.AddAccessRule(allowEveryoneRule);
_mutex.SetAccessControl(securitySettings);
}
public SingleGlobalInstance(int timeOut)
{
InitMutex();
try
{
if(timeOut < 0)
_hasHandle = _mutex.WaitOne(Timeout.Infinite, false);
else
_hasHandle = _mutex.WaitOne(timeOut, false);
if (_hasHandle == false)
throw new TimeoutException("Timeout waiting for exclusive access on SingleInstance");
}
catch (AbandonedMutexException)
{
_hasHandle = true;
}
}
public void Dispose()
{
if (_mutex != null)
{
if (_hasHandle)
_mutex.ReleaseMutex();
_mutex.Close();
}
}
}
Il existe une condition de concurrence critique dans la réponse acceptée lorsque 2 processus exécutés sous 2 utilisateurs différents tentent d'initialiser le mutex en même temps. Une fois que le premier processus a initialisé le mutex, si le deuxième processus tente de l'initialiser avant que le premier processus ne définisse la règle d'accès à tout le monde, une exception non autorisée sera levée par le deuxième processus.
Voir ci-dessous pour la réponse corrigée:
using System.Runtime.InteropServices; //GuidAttribute
using System.Reflection; //Assembly
using System.Threading; //Mutex
using System.Security.AccessControl; //MutexAccessRule
using System.Security.Principal; //SecurityIdentifier
static void Main(string[] args)
{
// get application GUID as defined in AssemblyInfo.cs
string appGuid = ((GuidAttribute)Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(GuidAttribute), false).GetValue(0)).Value.ToString();
// unique id for global mutex - Global prefix means it is global to the machine
string mutexId = string.Format( "Global\\{{{0}}}", appGuid );
bool createdNew;
// edited by Jeremy Wiebe to add example of setting up security for multi-user usage
// edited by 'Marc' to work also on localized systems (don't use just "Everyone")
var allowEveryoneRule = new MutexAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), MutexRights.FullControl, AccessControlType.Allow);
var securitySettings = new MutexSecurity();
securitySettings.AddAccessRule(allowEveryoneRule);
using (var mutex = new Mutex(false, mutexId, out createdNew, securitySettings))
{
// edited by acidzombie24
var hasHandle = false;
try
{
try
{
// note, you may want to time out here instead of waiting forever
// edited by acidzombie24
// mutex.WaitOne(Timeout.Infinite, false);
hasHandle = mutex.WaitOne(5000, false);
if (hasHandle == false)
throw new TimeoutException("Timeout waiting for exclusive access");
}
catch (AbandonedMutexException)
{
// Log the fact the mutex was abandoned in another process, it will still get aquired
hasHandle = true;
}
// Perform your work here.
}
finally
{
// edited by acidzombie24, added if statemnet
if(hasHandle)
mutex.ReleaseMutex();
}
}
}
Cet exemple se fermera après 5 secondes si une autre instance est déjà en cours d'exécution.
// unique id for global mutex - Global prefix means it is global to the machine
const string mutex_id = "Global\\{B1E7934A-F688-417f-8FCB-65C3985E9E27}";
static void Main(string[] args)
{
using (var mutex = new Mutex(false, mutex_id))
{
try
{
try
{
if (!mutex.WaitOne(TimeSpan.FromSeconds(5), false))
{
Console.WriteLine("Another instance of this program is running");
Environment.Exit(0);
}
}
catch (AbandonedMutexException)
{
// Log the fact the mutex was abandoned in another process, it will still get aquired
}
// Perform your work here.
}
finally
{
mutex.ReleaseMutex();
}
}
}
Ni Mutex ni WinApi CreateMutex () ne fonctionnent pour moi.
Une solution alternative:
static class Program
{
[STAThread]
static void Main()
{
if (SingleApplicationDetector.IsRunning()) {
return;
}
Application.Run(new MainForm());
SingleApplicationDetector.Close();
}
}
Et le SingleApplicationDetector
:
using System;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Threading;
public static class SingleApplicationDetector
{
public static bool IsRunning()
{
string guid = ((GuidAttribute)Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(GuidAttribute), false).GetValue(0)).Value.ToString();
var semaphoreName = @"Global\" + guid;
try {
__semaphore = Semaphore.OpenExisting(semaphoreName, SemaphoreRights.Synchronize);
Close();
return true;
}
catch (Exception ex) {
__semaphore = new Semaphore(0, 1, semaphoreName);
return false;
}
}
public static void Close()
{
if (__semaphore != null) {
__semaphore.Close();
__semaphore = null;
}
}
private static Semaphore __semaphore;
}
Raison d'utiliser Semaphore au lieu de Mutex:
La classe Mutex applique l'identité du thread, de sorte qu'un mutex ne peut être libéré que par le thread qui l'a acquis. En revanche, la classe Semaphore n’impose pas l’identité du thread.
Parfois, apprendre par l'exemple aide le plus. Exécutez cette application console dans trois fenêtres de console différentes. Vous verrez que l'application que vous avez exécutée en premier acquiert le mutex en premier, alors que les deux autres attendent leur tour. Puis appuyez sur Entrée dans la première application, vous verrez que l'application 2 continue à s'exécuter en acquérant le mutex, mais que l'application 3 attend son tour. Après avoir appuyé sur Entrée dans l'application 2, vous verrez que l'application 3 continue. Ceci illustre le concept d'un mutex protégeant une section de code à exécuter uniquement par un thread (dans ce cas un processus), comme l'écriture dans un fichier à titre d'exemple.
using System;
using System.Threading;
namespace MutexExample
{
class Program
{
static Mutex m = new Mutex(false, "myMutex");//create a new NAMED mutex, DO NOT OWN IT
static void Main(string[] args)
{
Console.WriteLine("Waiting to acquire Mutex");
m.WaitOne(); //ask to own the mutex, you'll be queued until it is released
Console.WriteLine("Mutex acquired.\nPress enter to release Mutex");
Console.ReadLine();
m.ReleaseMutex();//release the mutex so other processes can use it
}
}
}