2012-05-04 6 views
5

С помощью этого кода для очень простой регистратор:Почему блокировка в этом коде не работает?

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

, когда я пытаюсь его из нескольких потоков одновременно, я быстро получаю ошибку:

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

Почему замок не препятствует нити доступа файл в то же время?

Неважно, если потоки вызывали один и тот же экземпляр или разные экземпляры в один и тот же файл. Также я думал, что это может быть из-за некоторой отсрочки при записи файлов в Windows, но в Linux происходит то же самое.

+1

хеш-код! = Ссылка. Используйте 'ReferenceEquals'. –

+1

хэш-код! = Ссылка – TheBuzzSaw

+0

Хэш-код является дайджестом * содержимого * строки. Ссылка содержит сведения об этом конкретном экземпляре. Компилятор имеет опции для автоматического комбинирования жестко закодированных строк с идентичным содержимым, но во время выполнения не выполняется тонны ретро-сравнений, чтобы объединить несколько строк. – TheBuzzSaw

ответ

12

Вы блокируете временную строку. Вы должны ввести статический объект для блокировки.

+2

Shouldn ' t строка должна быть неизменной/сохранена только один раз независимо от того, сколько раз она была сделана? – zimdanen

+0

Почему неизменяемость релевантна? – spender

+0

@zimdanen Каждая из множества различных строк, которые были созданы, неизменна. Mutability не является проблемой. –

8

Создайте Dictionary<string,object> и сохраните объекты блокировки там с помощью пути к файлу.

Некоторое время назад, я подошел к этому же вопрос:

Locking by string. Is this safe/sane?

+2

Хотя этот ответ пропустит какой-то контекст, это правильный способ сделать это, если вы не хотите иметь единую блокировку для нескольких файлов. Найдите экземпляр в [этой реализации] (http://logging.codeplex.com/SourceControl/changeset/view/72677#1298869) некоторых [правил ведения журнала] (http://logging.codeplex.com).Эта реализация использует словарь 'OrdinalIgnoreCase'' и гарантирует, что канонические пути файлов используются (важны). – Steven

+0

@spender, это было то, что я закончил использовать и хорошо работает –

+0

@Steven, большое спасибо за ссылку –

4

Вы только блокировании динамически созданную строку ("LogWritter_" + this.FileName)! Каждый поток создаст еще один. Создать общую блокировку объекта вместо

public static readonly object fileLock = new object(); 

... 

lock (fileLock) { 
    ... 
} 

Если вы хотите, чтобы создать различные замки для различных файлов, вы должны будете хранить их в коллекции, которая будет использоваться для всех потоков.

Если вы работаете в .NET Framework 4.0, вы можете использовать ConcurrentDictionary<TKey, TValue>. В противном случае вам придется заблокировать доступ к нормальному 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

Если вы хотите сравнить ссылки двух строк, вы должны использовать

Object.ReferenceEquals(s1, s2) 

Где

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 

Строки, созданные во время компиляции, интернированы, то есть одна строковая константа будет создана для равных строк. Строки, созданные во время выполнения, однако, будут созданы как отдельные и разные объекты!

Хэш-код строк вычисляется по символам строки, а не по их ссылке.

+0

Я думаю, вы имеете в виду 'public static readonly object fileLock' :) – TheBuzzSaw

+0

Да, исправил. –

4

C# lock statement помещает замок на объект, а не уникальность строки. Итак, поскольку вы динамически объединяете две строки, вы по существу создаете новый объект каждый раз, поэтому каждый отдельный замок уникален. Даже если вы каждый раз обращаетесь к одному файлу, «A» + «B» приводит к некоторой новой неизменяемой строке; «A» + «B» снова приводит к еще одному новому объекту.

1

Пробуйте этот код.Когда первый поток приходит и вычисляет значение string.Concat («LogWritter_», this.FileName), он блокирует эту строку. Второй поток также вычисляет одно и то же строковое значение, но строки будут отличаться. Если вы сравните строки, используя ==, Equals() или GetHashCode(), вы увидите, что обе строки одинаковы, потому что == и Equals() перегружены для класса string. но если вы проверите ReferenceEquals(), то вы вернете false. Это означает, что обе строки имеют разные ссылки. И вот почему первый поток блокирует один строковый объект и второй поток блокирует второй объект-строку, и вы получаете ошибку.

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


} 
Смежные вопросы