簡體   English   中英

在 Powershell 中運行時與在 Visual Studio 中運行時的 HttpClient 並發行為不同

[英]HttpClient concurrent behavior different when running in Powershell than in Visual Studio

我正在使用 MS Graph API 將數百萬用戶從本地 AD 遷移到 Azure AD B2C,以在 B2C 中創建用戶。 我編寫了一個 .Net Core 3.1 控制台應用程序來執行此遷移。 為了加快速度,我正在對 Graph API 進行並發調用。 這很好用 - 有點。

在開發過程中,我從 Visual Studio 2019 運行時體驗到了可接受的性能,但為了測試,我從 Powershell 7 的命令行運行。從 Powershell 並發調用 HttpClient 的性能非常差。 從 Powershell 運行時,HttpClient 允許的並發調用數似乎存在限制,因此並發批處理中大於 40 到 50 個請求的調用開始堆積。 它似乎正在運行 40 到 50 個並發請求,同時阻止其余請求。

我不是在尋求異步編程方面的幫助。 我正在尋找一種方法來解決 Visual Studio 運行時行為和 Powershell 命令行運行時行為之間的差異。 從 Visual Studio 的綠色箭頭按鈕在發布模式下運行的行為與預期一致。 從命令行運行不會。

我用異步調用填充任務列表,然后等待 Task.WhenAll(tasks)。 每個調用需要 300 到 400 毫秒。 從 Visual Studio 運行時,它按預期工作。 我同時進行 1000 次調用,每個調用都在預期時間內單獨完成。 整個任務塊只比最長的單個調用長幾毫秒。

當我從 Powershell 命令行運行相同的構建時,行為會發生變化。 前 40 到 50 次調用預計需要 300 到 400 毫秒,但隨后各個調用時間會增加到每次 20 秒。 我認為調用是序列化的,所以一次只執行 40 到 50 個,而其他人則在等待。

經過數小時的反復試驗,我能夠將其縮小到 HttpClient。 為了隔離該問題,我使用執行 Task.Delay(300) 並返回模擬結果的方法模擬了對 HttpClient.SendAsync 的調用。 在這種情況下,從控制台運行的行為與從 Visual Studio 運行的行為相同。

我正在使用 IHttpClientFactory,我什至嘗試調整 ServicePointManager 上的連接限制。

這是我的注冊碼。

    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;
    }

這是 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;
    }
}

這是設置任務的代碼。

        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();

這是我模擬 HttpClient 的方法。

        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 個並發請求通過 GraphAPI 創建的 10k B2C 用戶的指標。 前 500 個請求比正常情況長,因為正在創建 TCP 連接。

這是控制台運行指標的鏈接。

這是Visual Studio 運行指標的鏈接。

VS 運行指標中的阻塞時間與我在這篇文章中所說的不同,因為我將所有同步文件訪問移到了進程的末尾,以盡可能隔離有問題的代碼以進行測試運行。

該項目是使用 .Net Core 3.1 編譯的。 我正在使用 Visual Studio 2019 16.4.5。

想到兩件事。 大多數 microsoft powershell 是在版本 1 和 2 中編寫的。版本 1 和 2 具有 MTA 的 System.Threading.Thread.ApartmentState。 在版本 3 到 5 中,公寓狀態默認更改為 STA。

第二個想法是聽起來他們正在使用 System.Threading.ThreadPool 來管理線程。 你的線程池有多大?

如果這些不能解決問題,請在 System.Threading 下開始挖掘。

當我讀到你的問題時,我想到了這個博客。 https://devblogs.microsoft.com/oldnewthing/20170623-00/?p=96455

一位同事演示了一個示例程序,該程序創建了一千個工作項,每個工作項都模擬一個需要 500 毫秒才能完成的網絡調用。 在第一個演示中,網絡調用是阻塞同步調用,示例程序將線程池限制為十個線程,以使效果更加明顯。 在這種配置下,前幾個工作項被快速分派到線程,但隨后延遲開始增加,因為沒有更多線程可用於為新工作項提供服務,因此剩余的工作項必須等待越來越長的時間才能讓線程可以為其提供服務。 工作項開始的平均延遲超過兩分鍾。

更新 1:我從開始菜單運行 PowerShell 7.0,線程狀態為 STA。 兩個版本的線程狀態不同嗎?

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

更新 2:我希望得到更好的答案,但是,您將比較這兩種環境,直到出現問題為止。

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

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

更新 3:

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

此外,每個 HttpClient 實例都使用自己的連接池,將其請求與其他 HttpClient 實例執行的請求隔離開來。

如果使用 HttpClient 和 Windows.Web.Http 命名空間中的相關類的應用下載了大量數據(50 兆字節或更多),則該應用應流式傳輸這些下載,而不是使用默認緩沖。 如果使用默認緩沖,客戶端內存使用量將變得非常大,可能會導致性能降低。

只要繼續比較這兩種環境,問題就會突出

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM