practice - lock task c#
¿Cómo encontrar que se adquiere Mutex en C#? (7)
¿Cómo puedo encontrar en el identificador de mutex en C # que se ha adquirido un mutex?
Cuando se mutex.WaitOne(timeout)
espera de mutex.WaitOne(timeout)
, devuelve false
. Sin embargo, ¿cómo puedo encontrar eso desde el manejador de exclusión mutua? (Tal vez usando p / invocar).
ACTUALIZACIÓN :
public class InterProcessLock : IDisposable
{
readonly Mutex mutex;
public bool IsAcquired { get; private set; }
public InterProcessLock(string name, TimeSpan timeout)
{
bool created;
var security = new MutexSecurity();
security.AddAccessRule(new MutexAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), MutexRights.Synchronize | MutexRights.Modify, AccessControlType.Allow));
mutex = new Mutex(false, name, out created, security);
IsAcquired = mutex.WaitOne(timeout);
}
#region IDisposable Members
public void Dispose()
{
if (IsAcquired)
{
mutex.ReleaseMutex();
IsAcquired = false;
}
}
#endregion
}
Actualmente, estoy usando mi propia propiedad IsAcquired
para determinar si debo liberar un mutex. No es esencial pero más claro, sería no usar una copia secundaria de la información representada por la propiedad de IsAcquired
, sino preguntar directamente a la IsAcquired
si la he adquirido yo. Desde que mutex.ReleaseMutex()
a mutex.ReleaseMutex()
lanza una excepción si no la he adquirido.
(Por estado adquirido me refiero a que el mutex está en un estado sin señalización cuando soy dueño del mutex).
(EDITAR: he añadido IsAcquired = false;
gracias a la publicación de mattdekrey ).
¿Por qué no puedes usar Mutex.OpenExisting
try
{
Mutex foundMutex = Mutex.OpenExisting("MyTestingMutex");
// Found Mutex
foundMutex.ReleaseMutex();
}
catch (System.Threading.WaitHandleCannotBeOpenedException)
{
// System.Threading.WaitHandleCannotBeOpenedException:
// The named mutex does not exist.
}
EDITAR
Estoy adivinando algo de esto.
Parece que estás intentando desarrollar una API. Uno de los elementos que ofrece en su API es un InterProcessLock.
Asumiré que está compartiendo una colección a través de subprocesos y que está usando el Mutex para asegurarse de que solo haya una operación en un momento.
using (InterProcessLock myLock = new InterProcessLock("LockMutex", TimeSpan.FromMilliseconds(100.0)))
{
if(myLock.IsAcquired)
{
// I have control then I can delete, add to the collection.
}
}
Me gustaría reconsiderar este diseño. ¿Qué InterProcessLock myLock = new InterProcessLock("LockMutex", TimeSpan.FromMilliseconds(100.0))
si nunca InterProcessLock myLock = new InterProcessLock("LockMutex", TimeSpan.FromMilliseconds(100.0))
en un uso? La disposición no se llamaría. ¿Qué pasa si el usuario nunca llama al Dispose?
Habría un Mutex abandonado.
Desde MSDN
Precaución Un mutex abandonado a menudo indica un error grave en el código. Cuando un hilo sale sin liberar el mutex, las estructuras de datos protegidas por el mutex podrían no estar en un estado coherente. El siguiente hilo para solicitar la propiedad del mutex puede manejar esta excepción y continuar, si se puede verificar la integridad de las estructuras de datos.
Si está tratando de proteger a sus usuarios, es posible que desee ayudarlos controlando el Mutex para que no tengan que preocuparse por eso.
Un posible ejemplo es
public static bool PerformLockedProcess(Action process, string commonLockName, TimeSpan timeout)
{
Mutex mutex = null;
// Get the Mutex for the User
try
{
bool created;
var security = new MutexSecurity();
security.AddAccessRule(new MutexAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), MutexRights.Synchronize | MutexRights.Modify, AccessControlType.Allow));
mutex = new Mutex(false, commonLockName, out created, security);
bool acquired = mutex.WaitOne(timeout);
if (acquired)
{
process();
return true;
}
return false;
}
finally
{
// Make sure we do not abandon the Mutex
if (mutex != null)
{
try
{
mutex.ReleaseMutex();
}
catch (ApplicationException)
{
// In case that failes
}
}
}
}
Esta es una forma posible. Todo depende de cuál sea el objetivo. NO retransmitiría al usuario final que llame a un Dispose dado que un Mutex es una construcción de sistema operativo. Y si el nombre no es unquie, podría afectar a otros procesos utilizando el mismo nombre de exclusión mutua.
Bueno, no es exactamente lo que está pidiendo, pero creo que resolvería su problema: ¿por qué no agregar un manejo de errores específicamente para la excepción que ocurre si el Mutex es adquirido por otra persona?
public void Dispose()
{
if (IsAcquired)
try
{ mutex.ReleaseMutex(); }
catch (System.Threading.SynchronizationLockException)
{
// Handle the exception, assuming you need to do anything.
// All other exceptions would still be passed up the stack.
}
}
Como puede encontrar, no hay miembros públicos en la clase Mutex
: http://msdn.microsoft.com/en-us/library/system.threading.mutex_members.aspx
Tampoco hay funciones nativas públicas para eso: http://msdn.microsoft.com/en-us/library/ms686360%28v=VS.85%29.aspx
Sin embargo, hay algunas funciones no documentadas / no compatibles, especialmente en ntdll.dll
. Estos permiten acceder a objetos del sistema. Sin embargo, estas funciones pueden cambiar o no estar disponibles en futuras versiones del sistema operativo.
Entonces, la respuesta es: No es posible usar medios convencionales.
Esto no beneficiará al póster original de la pregunta, pero aquí va.
Si bien no estoy en desacuerdo con otros afiches sobre el uso adecuado de Mutexes, tuve una aplicación en la que necesitaba probar si alguien es dueño de un mutex sin que yo mismo fuera el propietario. Como mencionaron otros, la única forma es usar una llamada al sistema NtQueryMutant no documentada desde ntdll.dll. Creé un método de extensión para la clase Mutex que se puede usar así:
bool createdNew = true;
var m = new Mutex(false, MutexName, out createdNew);
if ( m != null)
{
int currentCount;
bool ownedByCaller, abandonedState;
if (m.TryQuery(out currentCount, out ownedByCaller, out abandonedState))
{
Console.WriteLine(string.Format("Created New: {3}, Count: {0}, OwvedByMe: {1}, Abandoned: {2}",
currentCount, ownedByCaller, abandonedState, createdNew));
}
m.Close();
}
Y aquí está la implementación.
public static class MutexExtensionMethods
{
public static bool TryQuery(this Mutex m, out int currentCount, out bool ownedByCaller, out bool abandonedState)
{
currentCount = -1;
ownedByCaller = abandonedState = false;
try
{
var handle = m.SafeWaitHandle;
if (handle != null)
{
var h = handle.DangerousGetHandle();
MutantBasicInformation mbi;
int retLength;
var ntStatus = NtQueryMutant(
h,
MutantInformationClass.MutantBasicInformation,
out mbi,
Marshal.SizeOf(typeof(MutantBasicInformation)),
out retLength);
GC.KeepAlive(handle); // Prevent "handle" from being collected before NtQueryMutant returns
if (ntStatus == 0)
{
currentCount = mbi.CurrentCount;
ownedByCaller = mbi.OwnedByCaller;
abandonedState = mbi.AbandonedState;
return true;
}
}
}
catch
{
}
return false;
}
#region NTDLL.DLL
[DllImport("ntdll.dll")]
public static extern uint NtQueryMutant(
[In] IntPtr MutantHandle,
[In] MutantInformationClass MutantInformationClass,
[Out] out MutantBasicInformation MutantInformation,
[In] int MutantInformationLength,
[Out] [Optional] out int ReturnLength
);
public enum MutantInformationClass : int
{
MutantBasicInformation
}
[StructLayout(LayoutKind.Sequential)]
public struct MutantBasicInformation
{
public int CurrentCount;
[MarshalAs(UnmanagedType.U1)]
public bool OwnedByCaller;
[MarshalAs(UnmanagedType.U1)]
public bool AbandonedState;
}
#endregion
}
La clase .NET Mutex es un reiniciador de exclusión mutua nativa, que ofrece las mismas posibilidades, como la API de exclusión mutua nativa (excepto la espera de un número de objetos de otro tipo). Si desea adquirir mutex sin bloquear, llame a mutex.WaitOne (0). Usando PInvoke, puedes llamar a WaitForSingleObject, con el mismo resultado.
La razón por la que no hay una forma clara de hacerlo es porque no es una buena idea y porque las condiciones de la carrera son muy fáciles de introducir cuando confías en este tipo de lógica. Así que tu diseño necesita cambiar.
Primero, no debes adquirir un bloqueo en un constructor. Convierta esta clase en una fábrica que devuelva un objeto mutex debidamente inicializado. De esa manera podrás saber si adquiriste el candado o no.
NO confíe en Desechar para liberar bloqueos, esto requiere un código de interbloqueo difícil de mantener. Utilice un bloque try / finally para asegurarse de que se libera.
Los tiempos de espera son un poco vagos. Solo usar tiempos de espera cuando no se adquiere el bloqueo se consideraría operación normal. No poder adquirir el bloqueo suele ser un error y, simplemente, evitarlo con los tiempos de espera lo oculta. Si necesita tiempos de espera, considere usar un evento (tal vez AutoResetEvent), esto puede ser más apropiado.
Si realmente está tratando de hacer un bloqueo entre procesos, como su nombre lo indica, querrá una forma de detectar si el Mutex ha sido adquirido de todos modos, ¿correcto? No estoy seguro de cómo se aseguraría que su código que utiliza su InterProcessLock
se bloquee si no hubiera IsAcquired
propiedad IsAcquired
. (Además, para protegerme contra los programadores que accidentalmente llaman a Dispose dos veces, establecería el valor IsAcquired
en false
en su método de Dispose
).
Yo mismo implementé lo mismo (porque prefiero el uso de bloque a un intento, finalmente solo para liberar el mutex) y en su lugar, lancé una excepción cuando se excedió el tiempo de espera, lo cual, si recuerdo el proyecto correctamente, lo hizo. No llames al método Dispose.
Edición: beneficio adicional de lanzar la excepción en el constructor: su sección crítica también se evita por completo, y puede realizar el manejo de errores en el bloque catch
, que podría incluir el mismo método que tuvo su sección crítica, aunque personalmente lo consideraría Esa es una mala práctica.
Después de una reflexión adicional, en lugar de usar try ... catch
como se especifica en otra respuesta, puede usar lo siguiente en su disposición:
public void Dispose()
{
if (IsAcquired)
{
lock (mutex)
{
mutex.ReleaseMutex();
IsAcquired = false;
}
}
}
Se siente un poco irónico lock
un mutex, pero ahí lo tienen. Si bien estoy totalmente de acuerdo en que no debería confiar en que se llame a Dispose debido a la documentación con la interfaz IDisposable, creo que es increíblemente conveniente tener una sección crítica entre procesos indicada por un bloque using() { }
.