2

複数のユーザーが呼び出す可能性のあるプロセスがあります。これは非常にコストのかかるクエリですが、アーカイブ データを更新するために 5 分ごとに実行するだけで済みます。プロセスを一度に数回実行しないように、現在ロックを設定しています。これにより、システムがひざまずきますが、各ユーザーは、以前のロックが実行されるまで待機する必要があります。ロックを待機しているユーザーが 3 ~ 4 人いる場合、4 番目のユーザーはクエリが実行されるまで 20 分以上待機する必要があります。

私がやりたいことは、このオブジェクトをロックして、最初のリクエストでクエリを実行することです。他のリクエストが届いた場合は、現在のロックが終了するまで待機させてから、実際にクエリを実行せずに戻ります。

これを達成できる.Netに組み込まれているものはありますか、またはこのロック用の特定のコードを記述する必要がありますか?

4

2 に答える 2

3

ManualResetEventとロックでこれを行うことができます。

private object _dataLock = new object();
private ManualResetEvent _dataEvent = new ManualResetEvent(false);

private ArchiveData GetData()
{
    if (Monitor.TryEnter(_dataLock))
    {
        _dataEvent.Reset();  // makes other threads wait on the data

        // perform the query

        // then set event to say that data is available
        _dataEvent.Set();
        try
        {
            return data;
        }
        finally
        {
            Monitor.Exit(_dataLock);
        }
    }

    // Other threads wait on the event before returning data.
    _dataEvent.WaitOne();
    return data;
}

そのため、そこに到達した最初のスレッドがロックを取得して をクリアします_dataEvent。これは、他のスレッドがデータを待機する必要があることを示しています。ここには競合状態があり、2 番目のクライアントが_dataEventリセットされる前にそこに到達すると、古いデータが返されます。それがアーカイブ データであり、それが発生する機会が非常に少ないことを考えると、それは許容できると思います。

通過する他のスレッドは、ロックを取得しようとして失敗し、によってブロックされますWaitOne

データが利用可能になると、クエリを実行したスレッドがイベントを設定し、ロックを解除して、データを返します。

ロック本体全体を に入れていないことに注意してくださいtry...finally。その理由については、Eric Lippert のLocks and Exceptions do not mixを参照してください。

于 2013-06-17T21:04:25.570 に答える
2

この解決策は、複数の発信者が「準備コード」を実行する可能性を受け入れることができない人向けです。

この手法は、データが準備されたときの「通常の」ユース ケース シナリオでロックを使用することを回避します。ロックには多少のオーバーヘッドがあります。ユースケースに当てはまる場合と当てはまらない場合があります。

パターンはif-lock-ifパターン、IIRCと呼ばれます。私はできる限りインラインに注釈を付けようとしました:

bool dataReady;
string data;
object lock = new object();

void GetData()
{
    // The first if-check will only allow a few through. 
    // Normally maybe only one, but when there's a race condition 
    // there might be more of them that enters the if-block. 
    // After the data is ready though the callers will never go into the block, 
    // thus avoids the 'expensive' lock.
    if (!dataReady)
    {
        // The first callers that all detected that there where no data now
        // competes for the lock. But, only one can take it. The other ones
        // will have to wait before they can enter. 
        Monitor.Enter(lock);
        try
        {
            // We know that only one caller at the time is running this code
            // but, since all of the callers waiting for the lock eventually
            // will get here, we have to check if the data is still not ready.
            // The data could have been prepared by the previous caller,
            // making it unnecessary for the next callers to enter.
            if (!dataReady)
            { 
                // The first caller that gets through can now prepare and 
                // get the data, so that it is available for all callers.
                // Only the first caller that gets the lock will execute this code.
                data = "Example data";

                // Since the data has now been prepared we must tell any other callers that
                // the data is ready. We do this by setting the 
                // dataReady flag to true.
                Console.WriteLine("Data prepared!");
                dataReady = true;
            }
        }
        finally
        {
            // This is done in try/finally to ensure that an equal amount of 
            // Monitor.Exit() and Monitor.Enter() calls are executed. 
            // Which is important - to avoid any callers being left outside the lock. 
            Monitor.Exit(lock);
        }
    }

    // This is the part of the code that eventually all callers will execute,
    // as soon as the first caller into the lock has prepared the data for the others.
    Console.WriteLine("Data is: '{0}'", data);
}

MSDN リファレンス:

于 2016-06-21T11:34:21.553 に答える