2012-05-04 16 views
5

Con este código para un registrador muy básico:¿Por qué el bloqueo de este código no funciona?

lock (string.Concat("LogWritter_", this.FileName)) 
{ 
    using (var fileStream = File.Open(this.FileName, FileMode.Append, FileAccess.Write, FileShare.Read)) 
    { 
     using (var w = new StreamWriter(fileStream)) 
     { 
      w.Write(message); 
     } 
    } 
} 

cuando lo intento desde unos pocos hilos simultáneamente rápidamente me sale el error:

The process can't access the file because its being used by another file. 

Por qué el bloqueo no impide que los hilos de Acceso el archivo al mismo tiempo?

No importa si los hilos llaman a la misma instancia o instancias diferentes al mismo archivo. También pensé que podría ser debido a un aplazamiento al escribir archivos en Windows, pero en Linux sucede lo mismo.

+1

hash code! = Referencia. Use 'ReferenceEquals'. –

+1

hash code! = Reference – TheBuzzSaw

+0

El código hash es un resumen del * contenido * de la cadena. La referencia contiene los detalles con respecto a esa instancia particular. El compilador tiene opciones para combinar automáticamente cadenas codificadas con contenido idéntico, pero el tiempo de ejecución no hace toneladas de retrocomparaciones solo para combinar algunas cadenas. – TheBuzzSaw

Respuesta

12

Estás bloqueando una cadena temporal. Tienes que introducir un objeto estático para bloquearlo.

+2

Shouldn ' t la cadena sea inmutable/almacenado sólo una vez, no importa cuántas veces se ha hecho? – zimdanen

+0

¿Por qué es relevante la inmutabilidad? – spender

+0

@zimdanen cada una de las muchas series diferentes que se crean son inmutables. mutabilidad no es el problema. –

8

Crea un Dictionary<string,object> y almacena tus objetos de bloqueo allí con la ruta de archivo como clave.

Hace un tiempo, me acercó a esta misma pregunta:

Locking by string. Is this safe/sane?

+2

Aunque esta respuesta se pierde un poco de contexto, esta es la forma correcta de hacerlo si no quiere tener un candado para múltiples archivos. Busque por ejemplo en [esta implementación] (http://logging.codeplex.com/SourceControl/changeset/view/72677#1298869) de algunos [logging framework] (http://logging.codeplex.com).Esa implementación utiliza un 'Diccionario' OrdinalIgnoreCase' y asegura que se usen rutas de archivos canónicos (importantes). – Steven

+0

@spender, eso fue lo que terminé usando y funciona bien –

+0

@Steven, muchas gracias por el enlace –

4

Sólo se está fijando una cadena creada dinámicamente ("LogWritter_" + this.FileName)! Cada hilo creará otro. Crear un bloqueo de objeto común en lugar

public static readonly object fileLock = new object(); 

... 

lock (fileLock) { 
    ... 
} 

Si desea crear cerraduras diferentes para diferentes archivos, tendrá que guardarlos en una colección que será utilizado por todos los hilos.

Si está trabajando con .NET Framework 4.0, puede usar un ConcurrentDictionary<TKey, TValue>. De lo contrario, tendrá que bloquear el acceso a una normal Dictionary<TKey, TValue>

public static readonly ConcurrentDictionary<string,object> fileLocks = 
    new ConcurrentDictionary<string,object>(); 

... 

object lockObject = fileLocks.GetOrAdd(filename, k => new object()); 
lock (lockObject) { 
    ... 
} 

ACTUALIZACIÓN

Si desea comparar las referencias de dos cuerdas, usted tiene que utilizar

Object.ReferenceEquals(s1, s2) 

Donde

string s1 = "Hello"; 
string s2 = "Hello"; 
Console.WriteLine(Object.ReferenceEquals(s1, s2)); // ===> true 

string s3 = s1 + " World!"; 
string s4 = s2 + " World!"; 
Console.WriteLine(s3 == s4); // ===> true 
Console.WriteLine(Object.ReferenceEquals(s3, s4)); // ===> false 

Las cadenas creadas en tiempo de compilación son internas, es decir, se creará una única constante de cadena para cadenas iguales. Sin embargo, las cadenas creadas en tiempo de ejecución se crearán como objetos individuales y distintos.

El código hash de cadenas se calcula a partir de los caracteres de la cadena, no a partir de su referencia.

+0

Creo que quieres decir 'public static readonly object fileLock' :) – TheBuzzSaw

+0

Sí, lo corrigió. –

4

El C# lock statement coloca un bloqueo en el objeto, no la singularidad de la cadena. Entonces, como está concatenado dinámicamente dos cadenas, esencialmente está creando un nuevo objeto cada vez, por lo que cada bloqueo es único. Incluso si accede al mismo archivo cada vez, "A" + "B" da como resultado una nueva cadena inmutable; "A" + "B" nuevamente da como resultado otro objeto nuevo.

1

Pruebe este código.Cuando entra el primer subproceso y calcula el valor de string.Concat ("LogWritter_", this.FileName), pone un candado en esta cadena. El segundo subproceso también calculará el mismo valor de cadena pero las cadenas serán diferentes. Si compara cadenas usando ==, Equals() o GetHashCode(), verá que ambas cadenas son iguales porque == y Equals() están sobrecargados para la clase de cadena. pero si revisa los ReferenceEquals(), entonces devuelve falso. Significa que ambas cadenas tienen diferentes referencias. Y es por eso que el primer hilo se bloquea en un objeto de cadena y el segundo hilo se bloquea en el segundo objeto de cadena y se obtiene el error.

class Program 
{ 
    public static void Main(string[] args) 
    { 
     string locker = "str", temp = "temp"; 
     string locker1 = locker + temp; 
     string locker2 = locker + temp; 

     Console.WriteLine("HashCode{0} {1}", locker1.GetHashCode(), locker2.GetHashCode()); 
     Console.WriteLine("Equals {0}", locker1.Equals(locker2)); 
     Console.WriteLine("== {0}", locker1 == locker2); 
     Console.WriteLine("ReferenceEquals {0}", ReferenceEquals(locker1, locker2)); 
     app.Program p = new Program(); 
     Action<string> threadCall = p.Run; 
     threadCall.BeginInvoke(locker1, null, null); 
     threadCall.BeginInvoke(locker2, null, null); 
     Console.Read(); 
    } 

    public void Run(string str) 
    { 
     lock (str) 
     { 
      Console.WriteLine("im in"); 
      Thread.Sleep(4000); 
      Console.WriteLine("print from thread id {0}", Thread.CurrentThread.ManagedThreadId); 
     } 
    } 


} 
Cuestiones relacionadas