我尝试搜索此内容,但未找到最适合我所面临问题的建议。
我的问题是我们有可用资源的列表/堆栈(计算引擎)。这些资源用于执行某些计算。
执行计算的请求是从外部进程触发的。因此,当进行计算请求时,我需要检查是否有任何可用资源当前没有执行其他计算,如果是,请等待一段时间再检查一下。
我想知道实现这个的最佳方法是什么。我有以下代码,但不确定它是否非常安全。
如果您有任何进一步的建议,那就太棒了:
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);
}
}
答案 0 :(得分: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;
}
这分解了三个逻辑:
它还避免了使用递归,并将Monitor.Exit
调用放入finally
块,绝对应该在其中。
您可以使用以下方法替换中间方法实现:
return _connections.Any(TryProcess);
......但这对于自己的利益来说可能有点过于“聪明”。
就我个人而言,我很想将TryProcess
移到CalcEngineConnection
本身 - 这样这个代码不需要知道连接是否能够处理某事 - 这取决于对象本身。这意味着您可以避免公开可见的锁定,并且如果某些资源可以(比如说)在将来一次处理两个请求,它也会很灵活。
答案 1 :(得分: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()
。