サーチ…


構文

  • ロック(obj){}

備考

lockステートメントを使用すると、コードブロック内のコードへの異なるスレッドのアクセスを制御できます。競合状態を防ぐためによく使用されます。たとえば、複数のスレッドがコレクションから項目を読み込んだり、削除したりする場合などです。ロックがスレッドを強制的に他のスレッドがコードブロックを終了するのを待つようにすると、他の同期メソッドで解決できる遅延を引き起こす可能性があります。

MSDN

lockキーワードは、特定のオブジェクトに対する相互排他ロックを取得し、ステートメントを実行してロックを解放することによって、ステートメントブロックをクリティカルセクションとしてマークします。

lockキーワードは、別のスレッドがクリティカルセクションにある間に、あるスレッドがコードのクリティカルセクションに入らないようにします。別のスレッドがロックされたコードを入力しようとすると、オブジェクトが解放されるまでブロックされます。

ロックするプライベートオブジェクトを定義するか、 プライベート静的オブジェクト変数を定義してすべてのインスタンスに共通のデータを保護することをお勧めします。


C#5.0以降では、 lockステートメントは次のものと同等です。

bool lockTaken = false;
try 
{
    System.Threading.Monitor.Enter(refObject, ref lockTaken);
    // code 
}
finally 
{
    if (lockTaken)
        System.Threading.Monitor.Exit(refObject);
}

C#4.0以前では、 lockステートメントは次のものと同等です。

System.Threading.Monitor.Enter(refObject);
try 
{
    // code
}
finally 
{
     System.Threading.Monitor.Exit(refObject);
}

簡単な使い方

lock一般的な使用法は重要なセクションです。

次の例では、 ReserveRoomは異なるスレッドから呼び出されることになっています。ここで競合状態を防ぐ最も簡単な方法は、 lockとの同期です。メソッド本体は2つ以上のスレッドが同時に実行できないようにlockで囲まれていlock

public class Hotel
{
    private readonly object _roomLock = new object();

    public void ReserveRoom(int roomNumber)
    {
        // lock keyword ensures that only one thread executes critical section at once
        // in this case, reserves a hotel room of given number
        // preventing double bookings
        lock (_roomLock)
        {
            // reserve room logic goes here
        }
    }
}

あるスレッドが別のスレッドを実行している間にそのスレッドがlockブロックに到達すると、前のスレッドは別のスレッドをブロックしてブロックを終了します。

ロックするプライベートオブジェクトを定義するか、プライベート静的オブジェクト変数を定義してすべてのインスタンスに共通のデータを保護することをお勧めします。

ロックステートメントでの例外のスロー

次のコードはロックを解除します。問題はありません。裏側のロックステートメントは、 try finallyとして動作します

lock(locker)
{
    throw new Exception();
}

詳細は、 C#5.0仕様書を参照してください。

フォームのlockステートメント

lock (x) ...

ここで、 x参照型の式であり、

bool __lockWasTaken = false;
try {
    System.Threading.Monitor.Enter(x, ref __lockWasTaken);
    ...
}
finally {
    if (__lockWasTaken) System.Threading.Monitor.Exit(x);
}

ただし、 xは一度だけ評価されます。

ロック文での戻り値

次のコードはロックを解除します。

lock(locker)
{
    return 5;
}

詳細な説明については、 このSOの回答が推奨されます。

ロックのためにObjectのインスタンスを使用する

C#の組み込みlockステートメントを使用する場合、ある種のインスタンスが必要ですが、その状態は関係ありません。 objectのインスタンスはこれに最適です:

public class ThreadSafe {
  private static readonly object locker = new object();


  public void SomeThreadSafeMethod() {
    lock (locker) {
      // Only one thread can be here at a time.
    }
  }
}

NB 。インスタンスType (上記のコードでは、このために使用すべきではないtypeof(ThreadSafe)のインスタンスからである) Typeアプリケーションドメイン間で共有されているので、ロックの範囲は、予想通り、それはいけないコードを含むことができる(例えば、場合ThreadSafeにロードされます同じプロセス内に2つのAppDomainがあり、そのTypeインスタンスでロックすると相互にロックされます)。

反パターンと落書き

スタックに割り当てられた/ローカル変数に対するロック

lockを使用している間の誤りの1つは、関数内のロッカーとしてのローカルオブジェクトの使用です。これらのローカル・オブジェクト・インスタンスは関数の呼び出しごとに異なるため、 lockは期待どおりに実行されません。

List<string> stringList = new List<string>();

public void AddToListNotThreadSafe(string something)
{
    // DO NOT do this, as each call to this method 
    // will lock on a different instance of an Object.
    // This provides no thread safety, it only degrades performance.
    var localLock = new Object();
    lock(localLock)
    {
        stringList.Add(something);
    }
}

// Define object that can be used for thread safety in the AddToList method
readonly object classLock = new object();

public void AddToList(List<string> stringList, string something)
{
    // USE THE classLock instance field to achieve a 
    // thread-safe lock before adding to stringList
    lock(classLock)
    {
        stringList.Add(something);
    }
}

同期が同期オブジェクト自体へのアクセスを制限すると仮定すると

あるスレッドが: lock(obj)を呼び出し、別のスレッドがobj.ToString()呼び出すと、2番目のスレッドはブロックされません。

object obj = new Object();
 
public void SomeMethod()
{
     lock(obj)
    {
       //do dangerous stuff 
    }
 }

 //Meanwhile on other tread 
 public void SomeOtherMethod()
 {
   var objInString = obj.ToString(); //this does not block
 }

サブクラスがいつロックするかを知ることを期待する

時には、ベースクラスは、そのサブクラスが特定の保護されたフィールドにアクセスするときにロックを使用するように設計されています。

public abstract class Base
{
    protected readonly object padlock;
    protected readonly List<string> list;

    public Base()
    {
        this.padlock = new object();
        this.list = new List<string>();
    }

    public abstract void Do();
}

public class Derived1 : Base
{
    public override void Do()
    {
        lock (this.padlock)
        {
            this.list.Add("Derived1");
        }
    }
}

public class Derived2 : Base
{
    public override void Do()
    {
        this.list.Add("Derived2"); // OOPS! I forgot to lock!
    }
}

テンプレートメソッドを使用してロッキングカプセル化する方がはるかに安全です。

public abstract class Base
{
    private readonly object padlock; // This is now private
    protected readonly List<string> list;

    public Base()
    {
        this.padlock = new object();
        this.list = new List<string>();
    }

    public void Do()
    {
        lock (this.padlock) {
            this.DoInternal();
        }
    }

    protected abstract void DoInternal();
}

public class Derived1 : Base
{
    protected override void DoInternal()
    {
        this.list.Add("Derived1"); // Yay! No need to lock
    }
}

ボックス化されたValueType変数のロックが同期しない

次の例では、プライベート変数は、モニタリソースがロックされることを期待して、関数へのobject引数として提供されるため、暗黙的に囲まれていobject 。ボクシングは、IncInSync関数を呼び出す直前に発生します。したがって、ボックス化されたインスタンスは、関数が呼び出されるたびに別のヒープオブジェクトに対応します。

public int Count { get; private set; }

private readonly int counterLock = 1;

public void Inc()
{
    IncInSync(counterLock);
}

private void IncInSync(object monitorResource)
{
    lock (monitorResource)
    {
        Count++;
    }
}

ボクシングはInc関数で発生します:

BulemicCounter.Inc:
IL_0000:  nop         
IL_0001:  ldarg.0     
IL_0002:  ldarg.0     
IL_0003:  ldfld       UserQuery+BulemicCounter.counterLock
IL_0008:  box         System.Int32**
IL_000D:  call        UserQuery+BulemicCounter.IncInSync
IL_0012:  nop         
IL_0013:  ret         

これは、ボックス化されたValueTypeをモニタのロックに使用できないことを意味するものではありません。

private readonly object counterLock = 1;

今ボクシングはコンストラクタで発生しますが、これはロックに適しています:

IL_0001:  ldc.i4.1    
IL_0002:  box         System.Int32
IL_0007:  stfld       UserQuery+BulemicCounter.counterLock

より安全な代替手段が存在する場合にロックを不必要に使用する

非常に一般的なパターンは、プライベートListまたはDictionaryをスレッドセーフなクラスで使用し、アクセスするたびにロックすることです。

public class Cache
{
    private readonly object padlock;
    private readonly Dictionary<string, object> values;

    public WordStats()
    {
        this.padlock = new object();
        this.values = new Dictionary<string, object>();
    }
    
    public void Add(string key, object value)
    {
        lock (this.padlock)
        {
            this.values.Add(key, value);
        }
    }

    /* rest of class omitted */
}

values辞書にアクセスするメソッドが複数ある場合は、コードが非常に長くなる可能性があります。さらに重要なことは、すべての時間をロックすることでその意図が覆い隠されることです。ロックも非常に忘れやすいため、適切なロックがないとバグを見つけにくくなる可能性があります。

ConcurrentDictionaryを使用することで、完全にロックされないようにすることができます。

public class Cache
{
    private readonly ConcurrentDictionary<string, object> values;

    public WordStats()
    {
        this.values = new ConcurrentDictionary<string, object>();
    }
    
    public void Add(string key, object value)
    {
        this.values.Add(key, value);
    }

    /* rest of class omitted */
}

同時収集を使用すると、 すべてがロックフリー技術をある程度採用しているためパフォーマンスが向上します。



Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow