我的库有多个计时器(可以有数百个),它们都调用相同的逻辑(这是缓存清理,但这并不重要)。
这个逻辑非常消耗 CPU 资源,所以我引入了一个
lock
语句,以防止多个线程同时加载 CPU:
_timer = new Timer(EvictExpiredItems, null, interval, interval);
private void EvictExpiredItems(object state)
{
lock (_globalStaticLock)
{
DoStuff();
}
}
但是我担心的是 - 因为计时器回调是在线程池线程上执行的(?),而我实际上阻塞了该线程,这可能会导致线程饥饿(例如,在 ASP.NET 应用程序中)。
这是一个合理的担忧吗?
我的解决方案是进行计时器回调
async
并使用 SempahoreSlim
而不是 lock
。
private async void EvictExpiredItems(object state)
{
await _semaphoreSlim.WaitAsytnc(); //static semaphore var
try
{
DoStuff();
}
finally
{
_semaphoreSlim.Release();
}
}
这是一个合理的担忧吗?这是一个有效的解决方案吗?
是的,在计时器回调中使用
lock
,您将面临线程池饥饿的风险。 Timer
组件调用ThreadPool
上的回调,因此如果DoStuff
花费大量时间,并且在此期间回调调用比ThreadPool
中可用的线程多, ThreadPool
将变得饱和。这意味着新的工作请求不会立即得到满足,而是只有在线程可用时才会得到满足。 ThreadPool
有一种算法,可以在线程太少或太多时创建或销毁线程,因此饱和事件很少会导致死锁,但应用程序的性能和响应能力可能会受到明显的影响。如果我们谈论的是 ASP.NET 应用程序,那么应用程序的可扩展性也可能会受到严重损害。
我的建议是重新考虑你正在做的事情。您说您的应用程序有数百个计时器,它们都调用相同的逻辑。这对我来说听起来很恐怖。