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

我不确定 usingMonitor是这里最好的方法,但如果你决定走那条路,我会将上面的代码重构为:

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;
}

这分解了三个逻辑:

  • 重试几次
  • 尝试集合中的每个连接
  • 尝试单个连接

它还避免为此使用递归,并将Monitor.Exit调用放入一个绝对应该在finally其中的块中。

您可以将中间方法实现替换为:

return _connections.Any(TryProcess);

......但这可能有点太“聪明”了。

就我个人而言,我很想TryProcess进入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);
    }
}

这将正确保护您的连接,但请注意,这里的假设之一是您的_connections列表是安全的,并且不会被另一个线程修改。

此外,您可能希望使用线程安全队列,_connections因为在某些负载级别,您最终可能只使用前几个连接(不确定这是否会有所作为)。为了相对均匀地使用您的所有连接,我会将它们放入队列中并将它们出列。这也将保证没有两个线程使用相同的连接,并且您不必使用Monitor.TryEnter().

于 2012-11-21T18:22:13.060 回答