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 ステートメントは、文字列の一意性ではなく、オブジェクトをロックします。したがって、2 つの文字列を動的に連結しているため、基本的に毎回新しいオブジェクトを作成しているため、すべてのロックは一意です。毎回同じファイルにアクセスしたとしても、「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) {
    ...
}

アップデート

2 つの文字列の参照を比較する場合は、使用する必要があります。

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

コンパイル時に作成された文字列はインターンされます。つまり、等しい文字列に対して単一の文字列定数が作成されます。ただし、実行時に作成される文字列は、個別の個別のオブジェクトとして作成されます!

文字列のハッシュ コードは、参照からではなく、文字列の文字から計算されます。

于 2012-05-04T17:22:40.553 に答える
1

このコードを試してください。最初のスレッドが入ってstring.Concat( "LogWritter_"、this.FileName)の値を計算すると、この文字列がロックされます。2番目のスレッドも同じ文字列値を計算しますが、文字列は異なります。==、Equals()またはGetHashCode()を使用して文字列を比較すると、==とEquals()が文字列クラスに対してオーバーロードされているため、両方の文字列が同じであることがわかります。ただし、ReferenceEquals()を確認すると、falseが返されます。これは、両方の文字列が異なる参照を持っていることを意味します。そのため、最初のスレッドが1つの文字列オブジェクトをロックし、2番目のスレッドが2番目の文字列オブジェクトをロックすると、エラーが発生します。

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 に答える