我有一个在 IIS 7、.NET 4 上运行的 ASP.NET 应用程序,它偶尔需要与另一个 Web 服务通信(触发并忘记样式通知)。到目前为止,我曾经Thread
为每个我发现非常无效的通知触发一个(由于创建新线程的成本很高)。
出于这个原因,我开始使用BlockingCollection<>
容器来跟踪需要处理的通知,并Application_Start
使用此代码启动 3 个消费者来处理此队列:
Task.Factory.StartNew(Consumer.Create(sessionBuilder), TaskCreationOptions.LongRunning);
Task.Factory.StartNew(Consumer.Create(sessionBuilder), TaskCreationOptions.LongRunning);
Task.Factory.StartNew(Consumer.Create(sessionBuilder), TaskCreationOptions.LongRunning);
其中 sessionBuilder 只是一个提供数据库会话的对象。
消费者看起来像这样:
public static class Consumer
{
private static readonly ILog log = LogManager.GetLogger(typeof(Consumer));
public static Action Create(ISessionBuilder sessionBuilder)
{
return () =>
{
while (true)
{
try
{
if (RequestQueue.Requests.IsCompleted)
return;
var request = RequestQueue.Requests.Take();
Process(sessionBuilder, request);
}
catch (Exception e)
{
log.Error("Exception caught in the consumer's thread", e);
}
}
};
}
// The processing methods
//
}
虽然这似乎工作正常,但只要我将新版本的应用程序部署到 IIS,服务器的 CPU 就开始被我的w3wp
进程使用 99%。这绝对是我使用 BlockingQueue 带来的,在改变之前它并没有这样。
为了弄清楚 CPU 时间到底花在哪里,我让WMemoryProfiler和 WinDbg 随时查看所有托管线程的堆栈跟踪的捕获。
通过这种方式,我发现在我的进程 100% 使用 CPU 的那一刻,我有 17 个消费者线程正在运行,它们都将时间花在以下方面:
[GCFrame: 0000000012b2df58]
[HelperMethodFrame_1OBJ: 0000000012b2e048] System.Threading.Monitor.ObjWait(Boolean, Int32, System.Object)
System.Threading.SemaphoreSlim.WaitUntilCountOrTimeout(Int32, Int64, System.Threading.CancellationToken)
System.Threading.SemaphoreSlim.Wait(Int32, System.Threading.CancellationToken)
System.Collections.Concurrent.BlockingCollection`1[[System.__Canon, mscorlib]].TryTakeWithNoTimeValidation(System.__Canon ByRef, Int32, System.Threading.CancellationToken, System.Threading.CancellationTokenSource)
System.Collections.Concurrent.BlockingCollection`1[[System.__Canon, mscorlib]].TryTake(System.__Canon ByRef, Int32, System.Threading.CancellationToken)
System.Collections.Concurrent.BlockingCollection`1[[System.__Canon, mscorlib]].Take()
AppServer.Core.Queue.Consumer+<>c__DisplayClass1.b__0()
System.Threading.Tasks.Task.Execute()
System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
System.Threading.Tasks.Task.ExecuteWithThreadLocal(System.Threading.Tasks.Task ByRef)
System.Threading.Tasks.Task.ExecuteEntry(Boolean)
System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
System.Threading.ThreadHelper.ThreadStart(System.Object)
[GCFrame: 0000000012b2eae8]
[DebuggerU2MCatchHandlerFrame: 0000000012b2eed0]
[ContextTransitionFrame: 0000000012b2f068]
[DebuggerU2MCatchHandlerFrame: 0000000012b2f290]
所以,很明显,我在这里有一个问题。消费者不会在 AppDomain 卸载时终止(在部署后第一个 HTTP 请求到达时立即发生)。
1) 为什么消费者没有被终止?我以为那AppDomainUnloadException
是为了。
2)我是否应该有一些全局取消令牌,我会发出信号(但什么时候?)并让当前消费者知道他们的时间已经结束?或者这里推荐的模式是什么?
3) 17 个线程在上面显示的调用堆栈中花费时间充分利用 CPU 是否正常?或者,更确切地说,调用Take()
可以BlockingCollection<>
同时将这么多线程置于这种情况下是否正常?