1

我有一个在 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<>同时将这么多线程置于这种情况下是否正常?

4

0 回答 0