我的应用程序需要每分钟在每个租户上执行许多任务。这些是一劳永逸的操作,所以我不想使用Parallel.ForEach来处理此问题。
相反,我正在遍历租户列表,并触发ThreadPool.QueueUserWorkItem处理每个租户任务。
foreach (Tenant tenant in tenants)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(ProcessTenant), tenantAccount);
}
此代码在生产中可以完美运行,通常可以在5秒钟内处理100多个租户。
但是,在应用程序启动时,这会导致100%的CPU使用率,而诸如EF之类的东西会在启动过程中预热。为了限制这一点,我实现了如下信号灯:
private static Semaphore _threadLimiter = new Semaphore(4, 4);
这个想法是将任务处理限制为只能使用一半的计算机逻辑处理器。在ProcessTenant方法内部,我调用:
try
{
_threadLimiter.WaitOne();
// Perform all minute-to-minute tasks
}
finally
{
_threadLimiter.Release();
}
在测试中,这似乎完全符合预期。启动时的CPU使用率保持在50%左右,并且似乎不影响初始启动的速度。
所以问题主要是关于调用WaitOne时实际发生的事情。这是否释放线程以处理其他任务-与异步调用类似? MSDN文档指出,WaitOne:“阻塞当前线程,直到当前的WaitHandle收到信号为止。”
因此,我只是在警惕,这实际上不会允许我的Web应用程序在等待时继续使用此阻塞线程,这会使整个练习的意义变得毫无意义。
答案 0 :(得分:1)
WaitOne
确实会阻塞线程,并且该线程将停止在CPU内核上进行调度,直到发出信号量为止。但是,您可能会长时间从线程池中保留大量线程(“ long”(如“长于〜500 ms”)。这可能是一个问题,因为线程池的增长非常缓慢,因此您可能会阻止应用程序的其他部分正确使用它。
如果您打算等待大量时间,则可以使用自己的线程:
foreach (Tenant tenant in tenants)
{
new Thread(ProcessTenant).Start(tenantAccount);
}
但是,您仍然在内存中为每个项目保留一个线程。尽管他们因为在信号量上睡觉而不会吃掉CPU,但他们仍然没有使用RAM(每个线程大约1MB)。取而代之的是,让单个专用线程等待信号量,并根据需要排队新项目:
// Run this on a dedicated thread
foreach (Tenant tenant in tenants)
{
_threadLimiter.WaitOne();
ThreadPool.QueueUserWorkItem(_ =>
{
try
{
ProcessTenant(tenantAccount);
}
finally
{
_threadLimiter.Release();
}
});
}