使用此代码编写一个非常基本的记录器:
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上也会发生同样的事情。
答案 0 :(得分:12)
您正在锁定临时字符串。你必须引入一个静态对象来锁定。
答案 1 :(得分:8)
创建一个Dictionary<string,object>
并将锁定对象存储在那里,文件路径为关键。
前段时间,我接近同样的问题:
答案 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);
}
}
}