我正在为我们的服务实施断路器。这是非常标准的断路器,除了它将所有动作包装在超时中,因此当发生太多超时时它也会断开电路。当断路器变为半开状态时,我正在使用MonitorTryEnter
方法来确保只有一个线程执行操作(检查服务是否恢复)。请在下面找到代码片段(这是在半开状态下执行的代码):
编辑(粘贴整个功能代码)
public async Task<T> ExecuteWithCircuitBreaker<T>(Func<Task<T>> func)
{
if (!_circuitBreakerStateManager.IsInState(CircuitBreakerState.Closed))
{
if (HalfOpen())
{
bool lockTaken = false;
try
{
Monitor.TryEnter(_halfOpenSyncObject, ref lockTaken);
if (lockTaken)
{
var timeoutTask = Task.Delay(_options.ActionTimeout);
var resultTask = func.Invoke();
var firstFinishedTask = await Task.WhenAny(timeoutTask, resultTask);
if (firstFinishedTask == timeoutTask)
{
throw new TimeoutException($"Operation reached timeout configured for this circuit breaker. Timeout: {_options.ActionTimeout.TotalSeconds} s");
}
else
{
Reset();
return await resultTask;
}
}
}
catch (Exception ex)
{
Trip(ex);
throw;
}
finally
{
if (lockTaken)
{
Monitor.Exit(_halfOpenSyncObject);
}
}
}
throw new CircuitBreakerOpenException(_circuitBreakerStateManager.LastException);
}
// Circuit Breaker is closed - execute function normally
try
{
var timeoutTask = Task.Delay(_options.ActionTimeout);
var resultTask = func.Invoke();
var firstFinishedTask = await Task.WhenAny(timeoutTask, resultTask);
if (firstFinishedTask == timeoutTask)
throw new TimeoutException($"Operation reached timeout configured for this circuit breaker. Timeout: {_options.ActionTimeout.TotalSeconds} s");
else
return await resultTask;
}
catch (Exception ex)
{
Trip(ex);
throw;
}
}
正如您在上面的代码片段中看到的,TryEnter 被包裹在 Try/Finally 中,确保无论如何都会释放锁。我遇到的问题是,在某些时候Monitor.TryEnter
,每次尝试都开始返回 false。我调试了代码,发现在它开始发生之前,每次Monitor.TryEnter
调用都会Monitor.Exit
被执行——这意味着锁应该被释放,但此时似乎并不是每个调用都返回 false。不知道这怎么可能。
是否有可能Monitor.Exit
由于某种原因不释放锁(虽然它不会抛出任何异常)?或者我在这里做错了什么?
编辑。这就是 _halfOpenSyncObject 的声明方式(这个类只有一个实例)
public class TimeoutableCircuitBreaker: ICircuitBreaker
{
private readonly object _halfOpenSyncObject = new object();
...
编辑半开实现:
private bool HalfOpen()
{
var isHalfOpen = _circuitBreakerStateManager.GetLastStateChangedDate() + _options.BreakDuration < DateTime.UtcNow;
if (isHalfOpen)
_circuitBreakerStateManager.SetState(CircuitBreakerState.HalfOpen);
return isHalfOpen;
}