sistemas operativos lock c# concurrency mutex

operativos - ¿Cuál es un buen patrón para usar un Global Mutex en C#?



lock c# (6)

La clase Mutex es muy incomprendida, y Global se confunde aún más.

¿Cuál es el patrón bueno y seguro para usar al crear exclusión mutua global?

Uno que funcionara

  • Independientemente del lugar donde se encuentre mi máquina
  • Está garantizado para liberar el mutex correctamente
  • Opcionalmente no se cuelga para siempre si no se adquiere el mutex.
  • Se ocupa de casos donde otros procesos abandonan el mutex.

A veces, aprender con el ejemplo es lo que más ayuda. Ejecute esta aplicación de consola en tres ventanas de consola diferentes. Verás que la aplicación que ejecutaste primero adquiere el mutex primero, mientras que los otros dos esperan su turno. Luego, presione Entrar en la primera aplicación, verá que la aplicación 2 ahora continúa ejecutándose al adquirir el mutex, sin embargo, la aplicación 3 está esperando su turno. Después de presionar enter en la aplicación 2, verá que la aplicación 3 continúa. Esto ilustra el concepto de un mutex que protege una sección de código que se ejecutará solo por un hilo (en este caso, un proceso) como escribir en un archivo como ejemplo.

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 } } }


Este ejemplo se cerrará después de 5 segundos si ya se está ejecutando otra instancia.

// 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(); } } }


Hay una condición de carrera en la respuesta aceptada cuando 2 procesos se ejecutan bajo 2 usuarios diferentes que intentan inicializar el mutex al mismo tiempo. Después de que el primer proceso inicializa el mutex, si el segundo proceso intenta inicializar el mutex antes de que el primer proceso establezca la regla de acceso para todos, se generará una excepción no autorizada por el segundo proceso.

Vea a continuación la respuesta corregida:

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(); } } }


Ni Mutex ni WinApi CreateMutex () funcionan para mí.

Una solución alternativa:

static class Program { [STAThread] static void Main() { if (SingleApplicationDetector.IsRunning()) { return; } Application.Run(new MainForm()); SingleApplicationDetector.Close(); } }

Y el 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; }

Razón para usar Semaphore en lugar de Mutex:

La clase Mutex impone la identidad del subproceso, por lo que solo se puede liberar un mutex mediante el subproceso que lo adquirió. Por el contrario, la clase Semáforo no impone la identidad del hilo.

<< System.Threading.Mutex

Ref: Semaphore.OpenExisting()


Quiero asegurarme de que esto está ahí, porque es muy difícil hacerlo bien:

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(); } } }


Usando la respuesta aceptada, creo una clase de ayuda para que pueda usarla de la misma manera que usaría la instrucción Lock. Solo pensé que lo compartiría.

Utilizar:

using (new SingleGlobalInstance(1000)) //1000ms timeout on global lock { //Only 1 of these runs at a time RunSomeStuff(); }

Y la clase de ayuda:

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(); } } }