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



用这段代码做一个非常基本的记录器:

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

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

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

不久前,我遇到了同样的问题:

按字符串锁定。这是安全/理智的吗?

C# lock 语句在对象上放置一个锁,而不是字符串的唯一性。因此,由于您是动态连接两个字符串的,因此您实际上每次都在创建一个新对象,因此每个锁都是唯一的。即使您每次都访问相同的文件,"A"+"B"也会产生一些新的不可变字符串;"A"+"B"再次产生另一个新对象。

你只是锁定了一个动态创建的字符串("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

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

字符串的

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

试试这段代码。当第一个线程进来并计算 字符串。Concat("LogWritter_",这个。文件名)它对此字符串设置了一个锁。第二个线程也将计算相同的字符串值,但字符串将有所不同。如果你使用 ==,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);
        }
    }

}

最新更新