Powershell'de çalışırken HttpClient eşzamanlı davranışı Visual Studio'dakinden farklı


10

Milyonlarca kullanıcıyı, B2C'de kullanıcıları oluşturmak için MS Graph API'sını kullanarak şirket içi AD'den Azure AD B2C'ye geçiriyorum. Bu taşımayı gerçekleştirmek için bir .Net Core 3.1 konsol uygulaması yazdım. İşleri hızlandırmak için Grafik API'sına eşzamanlı çağrılar yapıyorum. Bu harika çalışıyor - bir çeşit.

Geliştirme sırasında Visual Studio 2019'dan çalışırken kabul edilebilir bir performans yaşadım, ancak test için Powershell 7'deki komut satırından çalıştırıyorum. Powershell'den HttpClient'e eşzamanlı çağrıların performansı çok kötü. Powershell'den çalıştırırken HttpClient'in izin verdiği eşzamanlı çağrı sayısında bir sınır var gibi görünüyor, bu nedenle 40 ila 50 istekden daha büyük eşzamanlı gruplar halinde çağrılar birikmeye başlıyor. Geri kalanını engellerken 40 ila 50 eşzamanlı istek çalıştırıyor gibi görünüyor.

Zaman uyumsuz programlama konusunda yardım aramıyorum. Visual Studio çalışma zamanı davranışı ve Powershell komut satırı çalışma zamanı davranışı arasındaki farkı vurmanın bir yolunu arıyorum. Visual Studio'nun yeşil ok düğmesinden serbest bırakma modunda çalıştırmak beklendiği gibi davranır. Komut satırından çalıştırma yapmaz.

Bir görev listesini zaman uyumsuz çağrılarla dolduruyorum ve sonra Task.WhenAll (görevler) 'i bekliyorum. Her çağrı 300 ila 400 milisaniye sürer. Visual Studio'dan çalıştırırken beklendiği gibi çalışır. Eşzamanlı olarak 1000 çağrı yaparım ve her biri beklenen süre içinde tamamlanır. Tüm görev bloğu, en uzun bireysel çağrıdan sadece birkaç milisaniye daha uzun sürer.

Powershell komut satırından aynı derlemeyi çalıştırdığımda davranış değişir. İlk 40 ila 50 çağrı beklenen 300 ila 400 milisaniye sürer, ancak bireysel çağrı sürelerinin her biri 20 saniyeye kadar uzar. Ben aramalar seri hale düşünüyorum, bu yüzden diğerleri beklerken bir anda sadece 40 ila 50 yürütülmektedir.

Saatlerce süren deneme yanılma işleminden sonra HttpClient'e daraltabiliyorum. Sorunu izole etmek için HttpClient.SendAsync çağrılarını Task.Delay (300) yapan bir yöntemle alay ettim ve bir sahte sonuç döndürür. Bu durumda, konsoldan çalışan Visual Studio'dan çalışanla aynı şekilde davranır.

IHttpClientFactory kullanıyorum ve hatta ServicePointManager bağlantı sınırını ayarlamaya çalıştım.

İşte kayıt kodum.

    public static IServiceCollection RegisterHttpClient(this IServiceCollection services, int batchSize)
    {
        ServicePointManager.DefaultConnectionLimit = batchSize;
        ServicePointManager.MaxServicePoints = batchSize;
        ServicePointManager.SetTcpKeepAlive(true, 1000, 5000);

        services.AddHttpClient(MSGraphRequestManager.HttpClientName, c =>
        {
            c.Timeout = TimeSpan.FromSeconds(360);
            c.DefaultRequestHeaders.Add("User-Agent", "xxxxxxxxxxxx");
        })
        .ConfigurePrimaryHttpMessageHandler(() => new DefaultHttpClientHandler(batchSize));

        return services;
    }

İşte DefaultHttpClientHandler.

internal class DefaultHttpClientHandler : HttpClientHandler
{
    public DefaultHttpClientHandler(int maxConnections)
    {
        this.MaxConnectionsPerServer = maxConnections;
        this.UseProxy = false;
        this.AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate;
    }
}

İşte görevleri ayarlayan kod.

        var timer = Stopwatch.StartNew();
        var tasks = new Task<(UpsertUserResult, TimeSpan)>[users.Length];
        for (var i = 0; i < users.Length; ++i)
        {
            tasks[i] = this.CreateUserAsync(users[i]);
        }

        var results = await Task.WhenAll(tasks);
        timer.Stop();

İşte ben HttpClient alay ettim.

        var httpClient = this.httpClientFactory.CreateClient(HttpClientName);
        #if use_http
            using var response = await httpClient.SendAsync(request);
        #else
            await Task.Delay(300);
            var graphUser = new User { Id = "mockid" };
            using var response = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(JsonConvert.SerializeObject(graphUser)) };
        #endif
        var responseContent = await response.Content.ReadAsStringAsync();

500 eşzamanlı istek kullanılarak GraphAPI aracılığıyla oluşturulan 10 bin B2C kullanıcısı için metrikler. TCP bağlantıları oluşturulduğu için ilk 500 istek normalden daha uzun.

İşte konsol çalıştırma metriklerine bir bağlantı .

İşte Visual Studio çalıştırma metriklerine bir bağlantı .

VS çalışma metriklerindeki blok süreleri, bu yayında söylediğimden farklıdır, çünkü tüm senkronize dosya erişimini, test kodları için mümkün olduğunca sorunlu kodu yalıtmak amacıyla sürecin sonuna taşıdım.

Proje .Net Core 3.1 kullanılarak derlenmiştir. Visual Studio 2019 16.4.5 kullanıyorum.


2
İlk toplu işlemden sonra netstat yardımcı programıyla bağlantılarınızın durumunu gözden geçirdiniz mi? İlk birkaç görev tamamlandıktan sonra neler olduğuna dair bir fikir verebilir.
Pranav Negandhi

Bu şekilde çözümlemezseniz (HTTP isteği Async), her zaman için bir ConcurrentQueue [nesne] tüketici / üretici paralelliğinde senkronize HTTP çağrılarını kullanabilirsiniz. Yakın zamanda bunu PowerShell'de yaklaşık 200 milyon dosya için yaptım.
thepip3r

1
@ thepip3r Az önce övgüyü tekrar okudum ve anladım. Aklımda tutarım.
Mark Lauter

1
Hayır diyorum, c # yerine PowerShell gitmek istiyorsanız: leeholmes.com/blog/2018/09/05/… .
thepip3r

1
@ thepip3r Sadece Stephen Cleary'nin blog girişini okuyun. İyi olmalıyım.
Mark Lauter

Yanıtlar:


3

akla iki şey gelir. Çoğu microsoft powershell sürüm 1 ve 2'de yazılmıştır. Sürüm 1 ve 2'de System.Threading.Thread.ApartmentState of MTA bulunur. 3'ten 5'e kadar sürümlerde apartman durumu varsayılan olarak STA olarak değiştirildi.

İkinci düşünce iş parçacıkları yönetmek için System.Threading.ThreadPool kullanıyor gibi geliyor. Threadpool ne kadar büyük?

Bunlar sorunu çözmezse System.Threading altında kazmaya başlayın.

Sorunuzu okuduğumda bu blogu düşündüm. https://devblogs.microsoft.com/oldnewthing/20170623-00/?p=96455

Bir meslektaş, her biri 500 ms süren bir ağ çağrısını simüle eden bin iş öğesi oluşturan örnek bir programla gösterdi. İlk gösteride, ağ çağrıları eşzamanlı çağrıları engelliyordu ve örnek program, efekti daha belirgin hale getirmek için iş parçacığı havuzunu on iş parçacığıyla sınırladı. Bu yapılandırma altında, ilk birkaç iş öğesi hızlı bir şekilde iş parçacıklarına gönderildi, ancak daha sonra yeni iş öğelerine hizmet verecek daha fazla iş parçacığı olmadığından gecikme oluşmaya başladı, bu nedenle kalan iş öğeleri bir iş parçacığının hizmet vermeye hazır hale gelir. İş öğesinin başlangıcındaki ortalama gecikme süresi iki dakikadan fazlaydı.

Güncelleme 1: Başlat menüsünden PowerShell 7.0'ı çalıştırdım ve iş parçacığı durumu STA idi. İş parçacığı durumu iki sürümde farklı mı?

PS C:\Program Files\PowerShell\7>  [System.Threading.Thread]::CurrentThread

ManagedThreadId    : 12
IsAlive            : True
IsBackground       : False
IsThreadPoolThread : False
Priority           : Normal
ThreadState        : Running
CurrentCulture     : en-US
CurrentUICulture   : en-US
ExecutionContext   : System.Threading.ExecutionContext
Name               : Pipeline Execution Thread
ApartmentState     : STA

Güncelleme 2: Daha iyi cevaplar dilerim, ancak bir şeyi fark edene kadar iki ortamı karşılaştırmış olacaksınız.

PS C:\Windows\system32> [System.Net.ServicePointManager].GetProperties() | select name

Name                               
----                               
SecurityProtocol                   
MaxServicePoints                   
DefaultConnectionLimit             
MaxServicePointIdleTime            
UseNagleAlgorithm                  
Expect100Continue                  
EnableDnsRoundRobin                
DnsRefreshTimeout                  
CertificatePolicy                  
ServerCertificateValidationCallback
ReusePort                          
CheckCertificateRevocationList     
EncryptionPolicy            

Güncelleme 3:

https://docs.microsoft.com/en-us/uwp/api/windows.web.http.httpclient

Ayrıca, her HttpClient örneği kendi bağlantı havuzunu kullanır ve isteklerini diğer HttpClient örnekleri tarafından yürütülen isteklerden yalıtır.

Windows.Web.Http ad alanında HttpClient ve ilgili sınıfları kullanan bir uygulama büyük miktarda veri (50 megabayt veya daha fazla) indirirse, uygulama bu indirmeleri akış olarak izlemeli ve varsayılan arabelleğe almayı kullanmamalıdır. Varsayılan arabellekleme kullanılırsa, istemci bellek kullanımı çok büyük olur ve bu da düşük performansa neden olur.

İki ortamı karşılaştırmaya devam edin ve sorun göze çarpmalıdır

Add-Type -AssemblyName System.Net.Http
$client = New-Object -TypeName System.Net.Http.Httpclient
$client | format-list *

DefaultRequestHeaders        : {}
BaseAddress                  : 
Timeout                      : 00:01:40
MaxResponseContentBufferSize : 2147483647

Powershell 7.0 System.Threading.Thread.CurrentThread.GetApartmentState () içinde çalışırken, Program.Main ()
Mark Lauter

Varsayılan min iş parçacığı havuzu 12 oldu, benim toplu iş boyutu (test için 500) min havuz boyutu artırmaya çalıştı. Bunun davranış üzerinde hiçbir etkisi yoktu.
Mark Lauter

Her iki ortamda da kaç tane iş parçacığı oluşturulur?
Aaron

Ben 'HttpClient' kaç konu olduğunu merak ediyordum çünkü tüm işi yapıyor.
Aaron

Her iki versiyonunuzda da apartman durumu nedir?
Aaron
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.