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

4

5 回答 5

12

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

于 2012-05-04T17:20:06.903 回答
8

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

不久前,我提出了同样的问题:

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

于 2012-05-04T17:21:27.117 回答
5

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

于 2012-05-04T17:24:00.123 回答
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

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

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

于 2012-05-04T17:22:40.553 回答
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);
        }
    }


}
于 2012-05-04T17:51:59.163 回答