Necesito bloquear una sección de código por cadena. Por supuesto, el siguiente código es terriblemente inseguro:Bloqueando por cadena. ¿Esto es seguro/sensato?
lock("http://someurl")
{
//bla
}
Así que he estado cocinando una alternativa. Normalmente no soy de los que publican grandes cantidades de código aquí, pero cuando se trata de programación concurrente, estoy un poco aprensivo acerca de hacer mi propio esquema de sincronización, así que estoy enviando mi código para preguntar si está bien hacerlo. de esta manera o si hay un enfoque más directo.
public class StringLock
{
private readonly Dictionary<string, LockObject> keyLocks = new Dictionary<string, LockObject>();
private readonly object keyLocksLock = new object();
public void LockOperation(string url, Action action)
{
LockObject obj;
lock (keyLocksLock)
{
if (!keyLocks.TryGetValue(url,
out obj))
{
keyLocks[url] = obj = new LockObject();
}
obj.Withdraw();
}
Monitor.Enter(obj);
try
{
action();
}
finally
{
lock (keyLocksLock)
{
if (obj.Return())
{
keyLocks.Remove(url);
}
Monitor.Exit(obj);
}
}
}
private class LockObject
{
private int leaseCount;
public void Withdraw()
{
Interlocked.Increment(ref leaseCount);
}
public bool Return()
{
return Interlocked.Decrement(ref leaseCount) == 0;
}
}
}
lo usaría como esto:
StringLock.LockOperation("http://someurl",()=>{
//bla
});
bueno para ir, o pesadez?
EDITAR
Para la posteridad, aquí es mi código de trabajo. Gracias por todas las sugerencias:
public class StringLock
{
private readonly Dictionary<string, LockObject> keyLocks = new Dictionary<string, LockObject>();
private readonly object keyLocksLock = new object();
public IDisposable AcquireLock(string key)
{
LockObject obj;
lock (keyLocksLock)
{
if (!keyLocks.TryGetValue(key,
out obj))
{
keyLocks[key] = obj = new LockObject(key);
}
obj.Withdraw();
}
Monitor.Enter(obj);
return new DisposableToken(this,
obj);
}
private void ReturnLock(DisposableToken disposableLock)
{
var obj = disposableLock.LockObject;
lock (keyLocksLock)
{
if (obj.Return())
{
keyLocks.Remove(obj.Key);
}
Monitor.Exit(obj);
}
}
private class DisposableToken : IDisposable
{
private readonly LockObject lockObject;
private readonly StringLock stringLock;
private bool disposed;
public DisposableToken(StringLock stringLock, LockObject lockObject)
{
this.stringLock = stringLock;
this.lockObject = lockObject;
}
public LockObject LockObject
{
get
{
return lockObject;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~DisposableToken()
{
Dispose(false);
}
private void Dispose(bool disposing)
{
if (disposing && !disposed)
{
stringLock.ReturnLock(this);
disposed = true;
}
}
}
private class LockObject
{
private readonly string key;
private int leaseCount;
public LockObject(string key)
{
this.key = key;
}
public string Key
{
get
{
return key;
}
}
public void Withdraw()
{
Interlocked.Increment(ref leaseCount);
}
public bool Return()
{
return Interlocked.Decrement(ref leaseCount) == 0;
}
}
}
utiliza como sigue:
var stringLock=new StringLock();
//...
using(stringLock.AcquireLock(someKey))
{
//bla
}
se ve más de la ingeniería para mí, pero es difícil de decir sin saber cuál es el problema que se supone que resolver. ¿Es solo para evitar bloquear una cadena? – LukeH
@LukeH, estoy tratando de escribir un caché de imágenes donde es probable que varios clientes (más de un 100) soliciten la imagen simultáneamente. El primer hit solicitará la imagen desde otro servidor, y me gustaría que todas las demás solicitudes a la caché se bloqueen hasta que se complete esta operación para que puedan recuperar la versión en caché. En mi opinión, esto requerirá bloqueo de esta naturaleza, pero si hay alguna alternativa, soy todo oídos. – spender
@Ani, sí, estoy al tanto de esto (como se describe en mi publicación) y si considero que este enfoque es un aficionado, reforzaré las defensas. – spender