2012-05-04 20 views
5

Con questo codice per un logger molto di base:Perché il blocco di questo codice non funziona?

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

quando provo da un paio di thread contemporaneamente ho ottenere rapidamente l'errore:

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

Perché il blocco non impedisce i fili per l'accesso il file allo stesso tempo?

Non importa se i thread chiamano la stessa istanza o diverse istanze nello stesso file. Inoltre ho pensato che potrebbe essere causa di un rinvio durante la scrittura di file in Windows, ma su Linux accade la stessa cosa.

+1

codice hash! = Riferimento. Utilizzare 'ReferenceEquals'. –

+1

codice hash! = Riferimento – TheBuzzSaw

+0

Il codice hash è un sommario del * contenuto * della stringa. Il riferimento contiene i dettagli relativi a quell'istanza particolare. Il compilatore ha opzioni per combinare automaticamente stringhe hard-coded con contenuti identici, ma il tempo di esecuzione non fa tonnellate di confronti retro solo per combinare alcune stringhe. – TheBuzzSaw

risposta

12

Si sta bloccando una stringa temporanea. Devi introdurre un oggetto statico da bloccare.

+2

Shouldn ' t la stringa non può essere immutata/memorizzata solo una volta, indipendentemente dal numero di volte in cui è stata creata? – zimdanen

+0

Perché l'immutabilità è rilevante? – spender

+0

@zimdanen Ciascuna delle molte stringhe distinte che sono state create è immutabile.Il problema non è la mutabilità –

8

Creare un Dictionary<string,object> e memorizzare gli oggetti di blocco lì con il percorso file come chiave.

Qualche tempo fa, mi sono avvicinato questa stessa domanda:

Locking by string. Is this safe/sane?

+2

Sebbene in questa risposta manchi qualche contesto, questo è il modo corretto per farlo se non si desidera avere un singolo blocco per più file. Guardate per esempio a [questa implementazione] (http://logging.codeplex.com/SourceControl/changeset/view/72677#1298869) di alcuni [framework di registrazione] (http://logging.codeplex.com).Tale implementazione utilizza un dizionario 'OrdinalIgnoreCase' e ​​garantisce che vengano utilizzati percorsi di file canonici (importante). – Steven

+0

@spender, questo è quello che ho finito di usare e funziona bene –

+0

@Steven, grazie mille per il link –

4

Siete solo il bloccaggio di una stringa creata dinamicamente ("LogWritter_" + this.FileName)! Ogni thread ne creerà un altro. Creazione di un lock-oggetto comune, invece

public static readonly object fileLock = new object(); 

... 

lock (fileLock) { 
    ... 
} 

Se si desidera creare serrature diverse per file diversi, sarà necessario memorizzarli in una collezione che verrà utilizzato da tutti i thread.

Se si utilizza .NET Framework 4.0, è possibile utilizzare uno ConcurrentDictionary<TKey, TValue>. In caso contrario, si dovrà bloccare l'accesso ad una normale Dictionary<TKey, TValue>

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

... 

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

UPDATE

Se si desidera confrontare i riferimenti delle due stringhe, è necessario utilizzare

Object.ReferenceEquals(s1, s2) 

Dove

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 

Le stringhe create in fase di compilazione sono internate, ovvero una costante di stringa singola verrà creata per stringhe uguali. Le stringhe create in fase di esecuzione, tuttavia, verranno create come oggetti singoli e distinti!

Il codice hash delle stringhe viene calcolato dai caratteri della stringa, non dal loro riferimento.

+0

Penso che tu intenda 'public statico readonly object fileLock' :) – TheBuzzSaw

+0

Sì, lo ha corretto. –

4

Il C# lock statement pone un blocco sull'oggetto, non sull'unicità della stringa. Quindi, poiché sei concatenato dinamicamente con due stringhe, in pratica stai creando un nuovo oggetto ogni volta, quindi ogni singolo blocco è unico. Anche se accedi sempre allo stesso file, "A" + "B" restituisce una nuova stringa immutabile; "A" + "B" restituisce ancora un altro nuovo oggetto.

1

Prova questo codice.Quando arriva il primo thread e calcola il valore di string.Concat ("LogWritter_", this.FileName) pone un blocco su questa stringa. Il secondo thread calcola anche lo stesso valore di stringa, ma le stringhe saranno diverse. Se confronterai le stringhe usando ==, Equals() o GetHashCode() vedrai che entrambe le stringhe sono uguali perché == e Equals() sono sovraccaricate per la classe string. ma se verificherete ReferenceEquals() allora restituite false. Significa che entrambe le stringhe hanno riferimenti diversi. Ed è per questo che il primo thread blocca su un oggetto stringa e il secondo blocco thread sul secondo oggetto stringa e si ottiene l'errore.

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


} 
Problemi correlati