为什么此代码中的锁不起作用?

时间:2012-05-04 17:19:04

标签: c# locking

使用此代码编写一个非常基本的记录器:

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上也会发生同样的事情。

5 个答案:

答案 0 :(得分:12)

您正在锁定临时字符串。你必须引入一个静态对象来锁定。

答案 1 :(得分:8)

创建一个Dictionary<string,object>并将锁定对象存储在那里,文件路径为关键。

前段时间,我接近同样的问题:

Locking by string. Is this safe/sane?

答案 2 :(得分:5)

C# lock statement锁定对象,而不是字符串的唯一性。因此,因为您是动态连接两个字符串,所以基本上每次创建一个新对象,因此每个锁都是唯一的。即使您每次都访问同一个文件,“A”+“B”也会产生一些新的不可变字符串; “A”+“B”再次导致另一个新对象。

答案 3 :(得分: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) {
    ...
}

<强>更新

如果要比较两个字符串的引用,则必须使用

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

在编译时创建的字符串被实现,即将为相等的字符串创建单个字符串常量。但是,在运行时创建的字符串将被创建为单独且不同的对象!

字符串的哈希码是根据字符串的字符计算的,而不是根据它们的引用计算的。

答案 4 :(得分:1)

试试这段代码。当第一个线程进入并计算值时  string.Concat(“LogWritter _”,this.FileName)它对此字符串进行了锁定。第二个线程也将计算相同的字符串值,但字符串将不同。如果您将使用==,Equals()或GetHashCode()比较字符串,您将看到两个字符串相同,因为==和Equals()为字符串类重载。但是如果你检查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);
        }
    }


}