Not
Bu sayfaya erişim yetkilendirme gerektiriyor. Oturum açmayı veya dizinleri değiştirmeyi deneyebilirsiniz.
Bu sayfaya erişim yetkilendirme gerektiriyor. Dizinleri değiştirmeyi deneyebilirsiniz.
Bu makale şunlar için geçerlidir: ✔️ .NET 9.0 ve üzeri sürümler
Bu öğreticide, bir ThreadPool açlık senaryosunu nasıl debug edeceğinizi öğreneceksiniz. ThreadPool sıkıntısı, havuzda yeni iş öğelerini işlemek için kullanılabilir iş parçacığı olmadığında yaşanır ve bu genellikle uygulamaların yanıt sürelerini yavaşlatır. Sağlanan örnek ASP.NET Core web uygulamasını kullanarak, ThreadPool'un açlıktan ölmesine neden olabilir ve bunu tanılamayı öğrenebilirsiniz.
Bu kılavuzda aşağıdakileri yapacaksınız:
- İsteklere yavaş yanıt veren bir uygulamayı araştırma
- ThreadPool'un aç kalmasının muhtemel olduğunu belirlemek için dotnet-counters aracını kullanın
- dotnet-stack ve dotnet-trace araçlarını kullanarak ThreadPool iş parçacıklarını meşgul eden çalışmayı belirleyin
Önkoşullar
Eğiticide şunlar kullanılıyor:
- Örnek uygulamayı derlemek ve çalıştırmak için .NET 9 SDK
- ThreadPool açlıktan ölme davranışını göstermek için örnek web uygulaması
- Örnek web uygulaması için yük oluşturmak için bombardier
- performans sayaçlarını gözlemlemek için dotnet-counters
- dotnet-stack iş parçacığı yığınlarını incelemek için kullanılır
- bekleme olaylarını toplamak için dotnet-trace
- İsteğe bağlı: Bekleme olaylarını analiz etmek için PerfView
Örnek uygulamayı çalıştırma
Örnek uygulamanın kodunu indirin ve .NET SDK'sını kullanarak çalıştırın:
E:\demo\DiagnosticScenarios>dotnet run
Using launch settings from E:\demo\DiagnosticScenarios\Properties\launchSettings.json...
info: Microsoft.Hosting.Lifetime[14]
Now listening on: https://localhost:5001
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://localhost:5000
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
Content root path: E:\demo\DiagnosticScenarios
Bir web tarayıcısı kullanıyor ve adresine https://localhost:5001/api/diagscenario/taskwaitistek gönderiyorsanız, yaklaşık 500 ms sonra döndürülen yanıtı success:taskwait görmeniz gerekir. Bu, web sunucusunun trafiğe beklendiği gibi hizmet ettiğini gösterir.
Yavaş performansı gözlemleme
Tanıtım web sunucusu, veritabanı isteğini taklit eden ve ardından kullanıcıya yanıt döndüren çeşitli uç noktalara sahiptir. Bu uç noktaların her biri, isteklere teker teker sunulurken yaklaşık 500 ms gecikmeye sahiptir, ancak web sunucusu bir miktar yüke maruz kaldığı zaman performans çok daha kötüdür. Bombardier yük testi aracını indirin ve her uç noktaya 125 eşzamanlı istek gönderildiğinde gecikme süresi farkını gözlemleyin.
bombardier-windows-amd64.exe https://localhost:5001/api/diagscenario/taskwait
Bombarding https://localhost:5001/api/diagscenario/taskwait for 10s using 125 connection(s)
[=============================================================================================] 10s
Done!
Statistics Avg Stdev Max
Reqs/sec 33.06 234.67 3313.54
Latency 3.48s 1.39s 10.79s
HTTP codes:
1xx - 0, 2xx - 454, 3xx - 0, 4xx - 0, 5xx - 0
others - 0
Throughput: 75.37KB/s
Bu ikinci uç nokta daha da kötü performans gösteren bir kod deseni kullanır:
bombardier-windows-amd64.exe https://localhost:5001/api/diagscenario/tasksleepwait
Bombarding https://localhost:5001/api/diagscenario/tasksleepwait for 10s using 125 connection(s)
[=============================================================================================] 10s
Done!
Statistics Avg Stdev Max
Reqs/sec 1.61 35.25 788.91
Latency 15.42s 2.18s 18.30s
HTTP codes:
1xx - 0, 2xx - 140, 3xx - 0, 4xx - 0, 5xx - 0
others - 0
Throughput: 36.57KB/s
Bu uç noktaların her ikisi de yük yüksek olduğunda (sırasıyla 3,48 sn ve 15,42 sn) 500 ms ortalama gecikme süresinden önemli ölçüde fazladır. Bu örneği .NET Core'un daha eski bir sürümünde çalıştırırsanız, her iki örneğin de aynı şekilde kötü performans sergilediğini görebilirsiniz. .NET 6, ilk örnekte kullanılan hatalı kodlama deseninin performans etkisini azaltan ThreadPool buluşsal yöntemlerini güncelleştirdi.
ThreadPool açlığı algılama
Yukarıdaki davranışı gerçek bir dünya hizmetinde gözlemlediyseniz yük altında yavaş yanıt verdiğini bilirsiniz ancak nedenini bilmezsiniz. dotnet-counters , canlı performans sayaçlarını gösterebilen bir araçtır. Bu sayaçlar belirli sorunlar hakkında ipuçları sağlayabilir ve genellikle kolayca elde edilebilir. Üretim ortamlarında, uzaktan izleme araçları ve web panoları tarafından sağlanan benzer sayaçlarınız olabilir. dotnet-counters'ı yükleyin ve web hizmetini izlemeye başlayın:
dotnet-counters monitor -n DiagnosticScenarios
Press p to pause, r to resume, q to quit.
Status: Running
Name Current Value
[System.Runtime]
dotnet.assembly.count ({assembly}) 115
dotnet.gc.collections ({collection})
gc.heap.generation
------------------
gen0 2
gen1 1
gen2 1
dotnet.gc.heap.total_allocated (By) 64,329,632
dotnet.gc.last_collection.heap.fragmentation.size (By)
gc.heap.generation
------------------
gen0 199,920
gen1 29,208
gen2 0
loh 32
poh 0
dotnet.gc.last_collection.heap.size (By)
gc.heap.generation
------------------
gen0 208,712
gen1 3,456,000
gen2 5,065,600
loh 98,384
poh 3,147,488
dotnet.gc.last_collection.memory.committed_size (By) 31,096,832
dotnet.gc.pause.time (s) 0.024
dotnet.jit.compilation.time (s) 1.285
dotnet.jit.compiled_il.size (By) 565,249
dotnet.jit.compiled_methods ({method}) 5,831
dotnet.monitor.lock_contentions ({contention}) 148
dotnet.process.cpu.count ({cpu}) 16
dotnet.process.cpu.time (s)
cpu.mode
--------
system 2.156
user 2.734
dotnet.process.memory.working_set (By) 1.3217e+08
dotnet.thread_pool.queue.length ({work_item}) 0
dotnet.thread_pool.thread.count ({thread}) 0
dotnet.thread_pool.work_item.count ({work_item}) 32,267
dotnet.timer.count ({timer}) 0
Uygulamanız .NET 9'dan daha eski bir .NET sürümü çalıştırıyorsa dotnet-counters çıkış kullanıcı arabirimi biraz farklı görünür; Ayrıntılar için dotnet-counters bölümüne bakın.
Yukarıdaki sayaçlar, web sunucusu herhangi bir istek sunmazken bir örnektir. Bombardier'i api/diagscenario/tasksleepwait uç noktası ve sabit yük ile 2 dakika boyunca yeniden çalıştırın, böylece performans sayaçlarında ne olduğunu gözlemlemek için bol bol zamanınız olur.
bombardier-windows-amd64.exe https://localhost:5001/api/diagscenario/tasksleepwait -d 120s
ThreadPool açlığı, kuyruğa alınan iş öğelerini işlemek için boş iş parçacığı olmadığında oluşur ve çalışma zamanı ThreadPool iş parçacığı sayısını artırarak yanıt verir.
dotnet.thread_pool.thread.count değeri, makinenizdeki işlemci çekirdeği sayısının 2-3 katına hızla yükselir ve daha sonra 125'in üzerinde bir yere sabitlenene kadar saniyede 1-2 iş parçacığı eklenmeye devam eder. ThreadPool açlığının şu anda bir performans darboğazı olduğunu belirten önemli sinyaller, ThreadPool iş parçacıklarının yavaş ve kararlı artışı ile CPU Kullanımı'nın 100%'nin oldukça altında olmasıdır. İş parçacığı sayısı artışı, havuz iş parçacığı sayısı üst sınırına ulaşana, tüm gelen iş öğelerini karşılamak için yeterli iş parçacığı oluşturulana veya CPU doygunluğa ulaşana kadar devam eder. Genellikle, ancak her zaman değil, ThreadPool yetersizliği durumunda dotnet.thread_pool.queue.length için büyük ve dotnet.thread_pool.work_item.count için düşük değerler gösterilir; bu ise çok miktarda işin beklemede olduğunu ve az sayıda işin tamamlandığını ifade eder. İş parçacığı sayısı hala artarken sayaçlara bir örnek aşağıda verilmiştir:
[System.Runtime]
dotnet.assembly.count ({assembly}) 115
dotnet.gc.collections ({collection})
gc.heap.generation
------------------
gen0 5
gen1 1
gen2 1
dotnet.gc.heap.total_allocated (By) 1.6947e+08
dotnet.gc.last_collection.heap.fragmentation.size (By)
gc.heap.generation
------------------
gen0 0
gen1 348,248
gen2 0
loh 32
poh 0
dotnet.gc.last_collection.heap.size (By)
gc.heap.generation
------------------
gen0 0
gen1 18,010,920
gen2 5,065,600
loh 98,384
poh 3,407,048
dotnet.gc.last_collection.memory.committed_size (By) 66,842,624
dotnet.gc.pause.time (s) 0.05
dotnet.jit.compilation.time (s) 1.317
dotnet.jit.compiled_il.size (By) 574,886
dotnet.jit.compiled_methods ({method}) 6,008
dotnet.monitor.lock_contentions ({contention}) 194
dotnet.process.cpu.count ({cpu}) 16
dotnet.process.cpu.time (s)
cpu.mode
--------
system 4.953
user 6.266
dotnet.process.memory.working_set (By) 1.3217e+08
dotnet.thread_pool.queue.length ({work_item}) 0
dotnet.thread_pool.thread.count ({thread}) 133
dotnet.thread_pool.work_item.count ({work_item}) 71,188
dotnet.timer.count ({timer}) 124
ThreadPool iş parçacıklarının sayısı dengeli hale geldikten sonra havuzda artık kaynak sıkıntısı yaşanmaz. Ancak yüksek bir değerde kararlı hale gelirse (işlemci çekirdeği sayısının yaklaşık üç katı), bu genellikle uygulama kodunun bazı ThreadPool iş parçacıklarını engellediğini ve ThreadPool'un daha fazla iş parçacığıyla çalıştırılarak telafi ettiğini gösterir. Yüksek iş parçacığı sayılarında sabit çalışmak, istek gecikme süresi üzerinde büyük bir etki yaratmayabilir, ancak yük zamanla önemli ölçüde değişirse veya uygulama belirli aralıklarla yeniden başlatılırsa, her seferinde ThreadPool'un iş parçacıklarını yavaşça artırarak yetersiz kaynak durumuna girip gecikme süresini kötüleştirmesi muhtemeldir. Her iş parçacığı bellek de tüketir, bu nedenle gereken toplam iş parçacığı sayısını azaltmak başka bir avantaj sağlar.
.NET 6'dan itibaren, ThreadPool sezgisel yöntemleri belirli engelleme Görev API'lerine yanıt olarak ThreadPool iş parçacığı sayısını çok daha hızlı artıracak şekilde değiştirildi. ThreadPool açlığı bu API'lerde yine de oluşabilir, ancak çalışma zamanı daha hızlı yanıt verdiği için süre eski .NET sürümlerinde olduğundan çok daha kısadır. Bombardier'i uç noktasıyla api/diagscenario/taskwait yeniden çalıştırın.
bombardier-windows-amd64.exe https://localhost:5001/api/diagscenario/taskwait -d 120s
.NET 6'da havuzun iş parçacığı sayısını öncekinden daha hızlı artırıp ve yüksek miktarda iş parçacığıyla kararlı hale geldiğini gözlemlemelisiniz. İş parçacığı sayısı tırmanırken ThreadPool açlığı oluşuyor.
ThreadPool aç kalma sorununu çözme
ThreadPool açlığını ortadan kaldırmak için ThreadPool iş parçacıklarının gelen iş öğelerini işlemek için kullanılabilir olması için engelsiz kalması gerekir. Her bir iş parçacığının ne yaptığını belirlemenin birden çok yolu vardır. Sorun yalnızca ara sıra oluşuyorsa, belirli bir süre boyunca uygulama davranışını kaydetmek için dotnet-trace ile bir izleme toplamak en iyisidir. Sorun sürekli oluşuyorsa dotnet-stack aracını kullanabilir veya Visual Studio'da görüntülenebilen dotnet-dump ile dökümü yakalayabilirsiniz. dotnet-stack, iş parçacığı yığınlarını hemen konsolda gösterdiği için daha hızlı olabilir. Ancak Visual Studio dökümü hata ayıklaması, çerçeveleri kaynağa eşleyen daha iyi görselleştirmeler sunar, Yalnızca Benim Kodum çalışma zamanı uygulama çerçevelerini filtreleyebilir ve Paralel Yığınlar özelliği benzer yığınlara sahip çok sayıda iş parçacığını gruplandırmaya yardımcı olabilir. Bu öğreticide dotnet-stack ve dotnet-trace seçenekleri gösterilir. Visual Studio kullanarak iş parçacığı yığınlarını araştırma örneği için Bkz. ThreadPool açlığı tanılama öğreticisi videosu.
dotnet-stack ile devam eden bir sorunu teşhis etme
Web sunucusunu yük altına almak için Bombardier'ı yeniden çalıştırın:
bombardier-windows-amd64.exe https://localhost:5001/api/diagscenario/taskwait -d 120s
Ardından iş parçacığı yığını izlerini görmek için dotnet-stack komutunu çalıştırın.
dotnet-stack report -n DiagnosticScenarios
Çok sayıda yığın içeren ve birçoğu aşağıdaki gibidir şeklinde uzun bir çıktı görmelisiniz.
Thread (0x25968):
[Native Frames]
System.Private.CoreLib.il!System.Threading.ManualResetEventSlim.Wait(int32,value class System.Threading.CancellationToken)
System.Private.CoreLib.il!System.Threading.Tasks.Task.SpinThenBlockingWait(int32,value class System.Threading.CancellationToken)
System.Private.CoreLib.il!System.Threading.Tasks.Task.InternalWaitCore(int32,value class System.Threading.CancellationToken)
System.Private.CoreLib.il!System.Threading.Tasks.Task`1[System.__Canon].GetResultCore(bool)
DiagnosticScenarios!testwebapi.Controllers.DiagScenarioController.TaskWait()
Anonymously Hosted DynamicMethods Assembly!dynamicClass.lambda_method1(pMT: 00007FF7A8CBF658,class System.Object,class System.Object[])
Microsoft.AspNetCore.Mvc.Core.il!Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor+SyncObjectResultExecutor.Execute(class Microsoft.AspNetCore.Mvc.Infrastructure.IActionResultTypeMapper,class Microsoft.Extensions.Internal.ObjectMethodExecutor,class System.Object,class System.Object[])
Microsoft.AspNetCore.Mvc.Core.il!Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync()
Microsoft.AspNetCore.Mvc.Core.il!Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(value class State&,value class Scope&,class System.Object&,bool&)
Microsoft.AspNetCore.Mvc.Core.il!Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()
Microsoft.AspNetCore.Mvc.Core.il!Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(value class State&,value class Scope&,class System.Object&,bool&)
Microsoft.AspNetCore.Mvc.Core.il!Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()
Microsoft.AspNetCore.Mvc.Core.il!Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(value class State&,value class Scope&,class System.Object&,bool&)
Microsoft.AspNetCore.Mvc.Core.il!Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
Microsoft.AspNetCore.Mvc.Core.il!Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(value class State&,value class Scope&,class System.Object&,bool&)
Microsoft.AspNetCore.Mvc.Core.il!Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
Microsoft.AspNetCore.Mvc.Core.il!Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeAsync()
Microsoft.AspNetCore.Mvc.Core.il!Microsoft.AspNetCore.Mvc.Routing.ControllerRequestDelegateFactory+<>c__DisplayClass10_0.<CreateRequestDelegate>b__0(class Microsoft.AspNetCore.Http.HttpContext)
Microsoft.AspNetCore.Routing.il!Microsoft.AspNetCore.Routing.EndpointMiddleware.Invoke(class Microsoft.AspNetCore.Http.HttpContext)
Microsoft.AspNetCore.Authorization.Policy.il!Microsoft.AspNetCore.Authorization.AuthorizationMiddleware+<Invoke>d__6.MoveNext()
System.Private.CoreLib.il!System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&)
Microsoft.AspNetCore.Authorization.Policy.il!Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(class Microsoft.AspNetCore.Http.HttpContext)
Microsoft.AspNetCore.HttpsPolicy.il!Microsoft.AspNetCore.HttpsPolicy.HttpsRedirectionMiddleware.Invoke(class Microsoft.AspNetCore.Http.HttpContext)
Microsoft.AspNetCore.HttpsPolicy.il!Microsoft.AspNetCore.HttpsPolicy.HstsMiddleware.Invoke(class Microsoft.AspNetCore.Http.HttpContext)
Microsoft.AspNetCore.HostFiltering.il!Microsoft.AspNetCore.HostFiltering.HostFilteringMiddleware.Invoke(class Microsoft.AspNetCore.Http.HttpContext)
Microsoft.AspNetCore.Server.Kestrel.Core.il!Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol+<ProcessRequests>d__223`1[System.__Canon].MoveNext()
System.Private.CoreLib.il!System.Threading.ExecutionContext.RunInternal(class System.Threading.ExecutionContext,class System.Threading.ContextCallback,class System.Object)
System.Private.CoreLib.il!System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[System.Threading.Tasks.VoidTaskResult,Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol+<ProcessRequests>d__223`1[System.__Canon]].MoveNext(class System.Threading.Thread)
System.Private.CoreLib.il!System.Threading.Tasks.AwaitTaskContinuation.RunOrScheduleAction(class System.Runtime.CompilerServices.IAsyncStateMachineBox,bool)
System.Private.CoreLib.il!System.Threading.Tasks.Task.RunContinuations(class System.Object)
System.IO.Pipelines.il!System.IO.Pipelines.StreamPipeReader+<<ReadAsync>g__Core|36_0>d.MoveNext()
System.Private.CoreLib.il!System.Threading.ExecutionContext.RunInternal(class System.Threading.ExecutionContext,class System.Threading.ContextCallback,class System.Object)
System.Private.CoreLib.il!System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[System.IO.Pipelines.ReadResult,System.IO.Pipelines.StreamPipeReader+<<ReadAsync>g__Core|36_0>d].MoveNext(class System.Threading.Thread)
System.Private.CoreLib.il!System.Threading.Tasks.AwaitTaskContinuation.RunOrScheduleAction(class System.Runtime.CompilerServices.IAsyncStateMachineBox,bool)
System.Private.CoreLib.il!System.Threading.Tasks.Task.RunContinuations(class System.Object)
System.Private.CoreLib.il!System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[System.Int32].SetExistingTaskResult(class System.Threading.Tasks.Task`1<!0>,!0)
System.Net.Security.il!System.Net.Security.SslStream+<ReadAsyncInternal>d__186`1[System.Net.Security.AsyncReadWriteAdapter].MoveNext()
System.Private.CoreLib.il!System.Threading.ExecutionContext.RunInternal(class System.Threading.ExecutionContext,class System.Threading.ContextCallback,class System.Object)
System.Private.CoreLib.il!System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[System.Int32,System.Net.Security.SslStream+<ReadAsyncInternal>d__186`1[System.Net.Security.AsyncReadWriteAdapter]].MoveNext(class System.Threading.Thread)
Microsoft.AspNetCore.Server.Kestrel.Core.il!Microsoft.AspNetCore.Server.Kestrel.Core.Internal.DuplexPipeStream+<ReadAsyncInternal>d__27.MoveNext()
System.Private.CoreLib.il!System.Threading.ExecutionContext.RunInternal(class System.Threading.ExecutionContext,class System.Threading.ContextCallback,class System.Object)
System.Private.CoreLib.il!System.Threading.ThreadPoolWorkQueue.Dispatch()
System.Private.CoreLib.il!System.Threading.PortableThreadPool+WorkerThread.WorkerThreadStart()
Bu yığınların altındaki çerçeveler, bu iş parçacıklarının ThreadPool iş parçacıkları olduğunu gösterir:
System.Private.CoreLib.il!System.Threading.ThreadPoolWorkQueue.Dispatch()
System.Private.CoreLib.il!System.Threading.PortableThreadPool+WorkerThread.WorkerThreadStart()
Üst kısımdaki çerçeveler, DiagnosticScenarioController.TaskWait() işlevinden yapılan çağrıda GetResultCore(bool) iş parçacığının engellendiğini gösterir:
Thread (0x25968):
[Native Frames]
System.Private.CoreLib.il!System.Threading.ManualResetEventSlim.Wait(int32,value class System.Threading.CancellationToken)
System.Private.CoreLib.il!System.Threading.Tasks.Task.SpinThenBlockingWait(int32,value class System.Threading.CancellationToken)
System.Private.CoreLib.il!System.Threading.Tasks.Task.InternalWaitCore(int32,value class System.Threading.CancellationToken)
System.Private.CoreLib.il!System.Threading.Tasks.Task`1[System.__Canon].GetResultCore(bool)
DiagnosticScenarios!testwebapi.Controllers.DiagScenarioController.TaskWait()
dotnet-trace ile kesintili bir sorunu tanılamak
dotnet-stack yaklaşımı yalnızca her istekte gerçekleşen belirgin ve tutarlı engelleme işlemleri için etkilidir. Bazı senaryolarda engelleme yalnızca birkaç dakikada bir düzensiz olarak gerçekleşir ve dotnet-stack sorunu tanılamak için daha az yararlı olur. Bu durumda dotnet-trace kullanarak belirli bir süre içindeki olayları toplayabilir ve daha sonra analiz edilebilen bir nettrace dosyasına kaydedebilirsiniz.
İş parçacığı havuzu açlığının teşhisine yardımcı olan belirli bir olay vardır: WaitHandleWait olayı, .NET 9'da tanıtılmıştır. Bir iş parçacığı, eşzamanlı-üzerinde-eşzamansız çağrılar (örneğin, Task.Result, Task.Wait, ve Task.GetAwaiter().GetResult()) veya lock, Monitor.Enter, ManualResetEventSlim.Wait, ve SemaphoreSlim.Wait gibi diğer kilitleme işlemleri tarafından engellendiğinde oluşur.
Web sunucusunu yük altına almak için Bombardier'ı yeniden çalıştırın:
bombardier-windows-amd64.exe https://localhost:5001/api/diagscenario/taskwait -d 120s
Ardından dotnet-trace komutunu çalıştırarak bekleme olaylarını toplayın:
dotnet trace collect -n DiagnosticScenarios --clrevents waithandle --clreventlevel verbose --duration 00:00:30
Bu, olayları içeren adlı DiagnosticScenarios.exe_yyyyddMM_hhmmss.nettrace bir dosya oluşturmalıdır. Bu nettrace iki farklı araç kullanılarak analiz edilebilir:
- PerfView: Yalnızca Windows için Microsoft tarafından geliştirilen bir performans analizi aracı.
- .NET Olay Görüntüleyicisi: Topluluk tarafından geliştirilen bir nettrace analizi Blazor web aracı.
Aşağıdaki bölümlerde nettrace dosyasını okumak için her aracın nasıl kullanılacağı gösterilmektedir.
Perfview ile bir nettrace analizi yapmak
PerfView'u indirin ve çalıştırın.
Nettrace dosyasını çift tıklayarak açın.
Gelişmiş Grup>Herhangi Bir Yığın'a çift tıklayın. Yeni bir pencere açılır.
"Microsoft-Windows-DotNETRuntime/WaitHandleWait/Start etkinlik çizgisine çift tıklayın."
Şimdi WaitHandleWait olaylarının yayıldığı yığın izlerini görmelisiniz. Bunlar "WaitSource" tarafından ayrılmıştır. Şu anda iki kaynak vardır:
MonitorWaitMonitor.Wait aracılığıyla yayılan olaylar veUnknowndiğerleri için.MonitorWait ile başlayın, çünkü olayların %64,8'i% temsil ediyor. Bu olayın yayılmasından sorumlu yığın izlemelerini genişletmek için onay kutularını işaretleyebilirsiniz.
Bu yığın izlemesi şu şekilde okunabilir:
Task<T>.Resultbir WaitSource MonitorWait ile WaitHandleWait olayı yaydı (Task<T>.Resultbekleme gerçekleştirmek içinMonitor.Waitkullanır).DiagScenarioController.TaskWaittarafından çağrıldı, bazı ASP.NET kodu tarafından çağrılan bir lambda tarafından çağrılmıştı.
.NET Olay Görüntüleyicisi ile nettrace çözümleme
Nettrace dosyasını sürükleyip bırakın.
Olay Ağacı sayfasına gidin, "WaitHandleWaitStart" olayını seçin ve ardından Sorguyu çalıştır'ı seçin.
WaitHandleWait olaylarının yayıldığı yığın izlemelerini görmeniz gerekir. Bu olayın yayılmasından sorumlu yığın izlemelerini genişletmek için oklara tıklayın.
Bu yığın izi şu şekilde okunabilir:
ManualResetEventSlim.Waitbir WaitHandleWait olayı yaydı.Task.SpinThenBlockWaittarafından çağrıldı,Task.InternalWaitCoretarafından çağrıldı,Task<T>.Resulttarafından çağrıldı,DiagScenario.TaskWaittarafından çağrıldı, bir lambda tarafından çağrıldı, bazı ASP.NET kodu tarafından çağrıldı.
Gerçek dünya senaryolarında, iş parçacığı havuzunun dışında kalan iş parçacıklarından yayılan çok sayıda bekleme olayıyla karşılaşabilirsiniz. Burada bir iş parçacığı havuzunun aç kalmasıyla ilgili araştırma yaptığınız için, iş parçacığı havuzunun dışındaki ayrılmış iş parçacığında tüm beklemeler ilgili değildir. Bir yığın izlemesinin bir iş parçacığı havuzu iş parçacığından olup olmadığını, ilk yöntemlere bakarak anlayabilirsiniz; bu yöntemlerde iş parçacığı havuzundan bahsedilmesi gerekebilir (örneğin, WorkerThread.WorkerThreadStart veya ThreadPoolWorkQueue).
Kod düzeltmesi
Artık zaman uyumsuz bir API'nin kullanılmadan çağrıldığını görmek için, örnek uygulamanın await dosyasındaki bu denetleyicinin koduna gidebilirsiniz. Bu, iş parçacıklarını engellediği bilinen ve ThreadPool kaynaklarının tükenmesinin en yaygın nedeni olan senkron-asenkron kod desenidir.
public ActionResult<string> TaskWait()
{
// ...
Customer c = PretendQueryCustomerFromDbAsync("Dana").Result;
return "success:taskwait";
}
Bu durumda kod, uç noktada gösterildiği TaskAsyncWait() gibi async/await kullanmak için kolayca değiştirilebilir. Await kullanılması, veritabanı sorgusu devam ederken geçerli iş parçacığının diğer iş öğelerine hizmet vermesine olanak tanır. Veritabanı araması tamamlandığında ThreadPool iş parçacığı yürütmeyi sürdürür. Bu şekilde her istek sırasında kodda hiçbir iş parçacığı engellenmez.
public async Task<ActionResult<string>> TaskAsyncWait()
{
// ...
Customer c = await PretendQueryCustomerFromDbAsync("Dana");
return "success:taskasyncwait";
}
Bombardier yükü api/diagscenario/taskasyncwait uç noktasına göndermek için çalıştırıldığında, ThreadPool iş parçacığı sayısının çok daha düşük kaldığını ve asenkron/await yaklaşımı kullanılırken ortalama gecikme süresinin 500 ms civarında kaldığını gösterir.
>bombardier-windows-amd64.exe https://localhost:5001/api/diagscenario/taskasyncwait
Bombarding https://localhost:5001/api/diagscenario/taskasyncwait for 10s using 125 connection(s)
[=============================================================================================] 10s
Done!
Statistics Avg Stdev Max
Reqs/sec 227.92 274.27 1263.48
Latency 532.58ms 58.64ms 1.14s
HTTP codes:
1xx - 0, 2xx - 2390, 3xx - 0, 4xx - 0, 5xx - 0
others - 0
Throughput: 98.81KB/s