一度に 1 つのスレッドだけが使用する必要があるオブジェクトがあります。たとえば、私のオブジェクトには 3 つのメソッドが含まれておりA、BスレッドCがメソッドにアクセスする場合はオブジェクトをロックします (すべてのメソッド/属性がロックされます) A。
主な問題は、そのオブジェクトのコードを変更できないことです。オブジェクトを呼び出しているマルチスレッド アクセスを防止する必要があります。
最初はシングルトン パターンを使用することを考えましたが、うまく機能しませんでした。
一度に 1 つのスレッドだけが使用する必要があるオブジェクトがあります。たとえば、私のオブジェクトには 3 つのメソッドが含まれておりA、BスレッドCがメソッドにアクセスする場合はオブジェクトをロックします (すべてのメソッド/属性がロックされます) A。
主な問題は、そのオブジェクトのコードを変更できないことです。オブジェクトを呼び出しているマルチスレッド アクセスを防止する必要があります。
最初はシングルトン パターンを使用することを考えましたが、うまく機能しませんでした。
オブジェクトのコードを変更できない場合は、オブジェクトの外側でロックを処理する必要があります。たとえば、別のクラスにカプセル化して (インターフェイスの背後に隠している可能性があります)、そのラッパー クラスに同期を適用させることができます。
public class Foo {
private readonly YourType tail;
private readonly object syncLock = new object();
public Foo(YourType tail) {this.tail = tail;}
public A() { lock(syncLock) { tail.A(); } }
public B() { lock(syncLock) { tail.B(); } }
public C() { lock(syncLock) { tail.C(); } }
}
シングルトン パターンは、ここでは適切ではありません。オブジェクトのインスタンスが 1 つだけであることを保証しますが、それをどのように使用できるかを指示しません。
コードのスレッド セーフは、そのコード内で定義する必要があります。つまり、オブジェクトのコードを変更できない場合、適切にスレッドセーフにすることはできません。ただし、回避策があります。作成した新しいクラスでオブジェクトをラップし、新しいオブジェクトがスレッドセーフであることを確認してください。安全でないオブジェクトのメソッドに対してスレッドセーフなラッパーを公開することで、希望どおりにアクセスできるようになります。
これを行う最も簡単な方法は、lockキーワードを使用することです。このようなものがうまくいくかもしれません:
public class ThreadSafeThing
{
private UnsafeThing _thing = new UnsafeThing();
private object _syncRoot = new object();
public void DoSomething() // this is your thread-safe version of Thing.DoSomething
{
lock (_syncRoot)
{
_thing.DoSomething();
}
}
}
OPは指定しませんでしたが、彼のシーンに複数のクライアント呼び出し間でオブジェクトをロックしておく必要がある可能性が含まれている場合(たとえば、クライアントから関数Aを呼び出す必要があり、結果に応じて関数BまたはCを呼び出します。オブジェクトを常に他のスレッドにロックしたままにする)、少し異なる方法で実装する必要があります。例:
public static class ThreadSafeThing {
private static UnsafeThing _thing = new UnsafeThing();
private static readonly object _lock = new object();
public static void getLock() {
Monitor.Enter(_lock);
}
public static void releaseLock() {
Monitor.Exit(_lock);
}
// this is your thread-safe version of Thing.DoSomething
public static bool DoSomething() {
try {
Monitor.Enter(_lock);
return _thing.DoSomething();
}
finally {
Monitor.Exit(_lock);
}
}
// this is your thread-safe version of Thing.DoSomethingElse
public static void DoSomethingElse() {
try {
Monitor.Enter(_lock);
return _thing.DoSomethingElse();
}
finally {
Monitor.Exit(_lock);
}
}
}
クライアントからの呼び出しから...
try {
ThreadSafeThing.getLock();
if (ThreadSafeThing.DoSomething()) {
ThreadSafeThing.DoSomethingElse();
}
}
finally {
// This must be called no matter what happens
ThreadSafeThing.releaseLock();
}
ここでの主な違いは、クライアントがロックを取得し、ロックが終了したら解放する責任があることです。これにより、ロックを維持しながら、オブジェクト全体で複数の関数を呼び出すことができます。releaseLock を使用してロックが解放されるまで、他のすべてのスレッドは getLock 呼び出しでブロックされます。
編集: DoSomething および DoSomethingElse メソッドにロックの自動取得を追加し、最初に getLock メソッドを介してロックを取得せずにこれらのメソッドを直接呼び出すと、スレッドが使い捨てロックも取得できるようにしました。ただし、この方法でロックを取得した場合、そのロックは 1 回のメソッド呼び出しの間しか持続しないことに注意してください。
スレッドごとに 1 つのオブジェクトだけを作成することはできないと仮定すると、別の方法として、もう 1 つのスレッドを生成して非スレッドセーフ オブジェクトのメソッドを呼び出し、その 1 つのスレッドへの呼び出し要求をキューに入れます。通常、スレッドは、非スレッドセーフ オブジェクトで要求された操作を実行した後、キューに入れられた要求で提供される 'OnCompletion' コールバックを起動する必要があります。
その後、操作は非同期で実行されますが、要求をキューに入れ、コールバックによって通知されるイベントを待機することで、同期呼び出しを行うことができます。
..ラッパーオブジェクトでの単純なロックよりも柔軟なもう1つの可能性。