3

これを検索してみましたが、直面している問題に最適な提案が見つかりませんでした。

私の問題は、利用可能なリソース (計算エンジン) のリスト/スタックがあることです。これらのリソースは、特定の計算を実行するために使用されます。

計算を実行するリクエストは、外部プロセスからトリガーされます。そのため、計算のリクエストが行われたときに、利用可能なリソースのいずれかが現在他の計算を実行していないかどうかを確認する必要があります。そうであれば、しばらく待ってからもう一度確認してください。

これを実装する最良の方法は何だろうと思っていました。次のコードが配置されていますが、非常に安全かどうかはわかりません。

さらに提案があれば、それは素晴らしいことです:

void Process(int retries = 0) {
    CalcEngineConnection connection = null;
    bool securedConnection = false;
    foreach (var calcEngineConnection in _connections) {
        securedConnection = Monitor.TryEnter(calcEngineConnection);
        if (securedConnection) {
            connection = calcEngineConnection;
            break;
        }
    }
    if (securedConnection) {
        //Dequeue the next request
        var calcEnginePool = _pendingPool.Dequeue();

        //Perform the operation and exit.
        connection.RunCalc(calcEnginePool);
        Monitor.Exit(connection);
    }
    else {
        if (retries < 10)
            retries += 1;
        Thread.Sleep(200);
        Process(retries);
    }
}
4

2 に答える 2

2

Monitorとにかく、ここで使用することが最善のアプローチであるかどうかはわかりませんが、そのルートに進むことにした場合は、上記のコードを次のようにリファクタリングします。

bool TryProcessWithRetries(int retries) {
    for (int attempt = 0; attempt < retries; attempt++) {
        if (TryProcess()) {
            return true;
        }
        Thread.Sleep(200);
    }
    // Throw an exception here instead?
    return false;
}

bool TryProcess() {
    foreach (var connection in _connections) {
        if (TryProcess(connection)) {
            return true;
        }
    }
    return false;
}

bool TryProcess(CalcEngineConnection connection) {
    if (!Monitor.TryEnter(connection)) {
        return false;
    }
    try {
        var calcEnginePool = _pendingPool.Dequeue();
        connection.RunCalc(calcEnginePool);
    } finally {
        Monitor.Exit(connection);
    }
    return true;
}

これにより、次の 3 つのロジックが分解されます。

  • 何度かリトライ
  • コレクション内の各接続を試す
  • 単一接続の試行

また、そのために再帰を使用することを避け、Monitor.Exit呼び出しを絶対finallyにブロックに入れます。

中間メソッドの実装を次のように置き換えることができます。

return _connections.Any(TryProcess);

...しかし、それはそれ自体の利益のためには少し「賢すぎる」かもしれません。

個人的には、それ自体に移行TryProcessしたくなるでしょう。この方法では、このコードは、接続が何かを処理できるかどうかを知る必要がありません。それはオブジェクト自体次第です。これは、一般に公開されているロックを回避できることを意味します。また、将来、一部のリソースが一度に 2 つのリクエストを (たとえば) 処理できる場合にも柔軟に対応できます。CalcEngineConnection

于 2012-11-21T18:21:12.307 に答える
1

発生する可能性のある問題は複数ありますが、最初にコードを単純化しましょう。

void Process(int retries = 0) 
{
    foreach (var connection in _connections) 
    {
        if(Monitor.TryEnter(connection))
        {
            try
            {
                //Dequeue the next request
                var calcEnginePool = _pendingPool.Dequeue();

                //Perform the operation and exit.
                connection.RunCalc(calcEnginePool);
            }
            finally
            {
                // Release the lock
                Monitor.Exit(connection);
            }
            return;
        }
    }

    if (retries < 10)
    {
        Thread.Sleep(200);
        Process(retries+1);
    }
}

これにより接続が正しく保護されますが、ここでの前提条件の1つは、_connectionsリストが安全であり、別のスレッドによって変更されないことです。

_connectionsさらに、特定の負荷レベルでは最初のいくつかの接続のみを使用する可能性があるため、スレッドセーフキューを使用することをお勧めします(それが違いを生むかどうかはわかりません)。すべての接続を比較的均等に使用するために、それらをキューに入れてデキューします。これにより、2つのスレッドが同じ接続を使用しておらず、を使用する必要がないことも保証されますMonitor.TryEnter()

于 2012-11-21T18:22:13.060 に答える