gpt4 book ai didi

c# - 在 Powershell 中运行时与在 Visual Studio 中运行时的 HttpClient 并发行为不同

转载 作者:行者123 更新时间:2023-12-02 22:10:08 29 4
gpt4 key购买 nike

我正在使用 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 的绿色箭头按钮在 Release模式下运行的行为符合预期。从命令行运行不会。

我用异步调用填充任务列表,然后等待 Task.WhenAll(tasks)。每次调用需要 300 到 400 毫秒。从 Visual Studio 运行时,它按预期工作。我同时进行了 1000 个调用,每个调用都在预期时间内单独完成。整个任务 block 只比最长的单个调用长几毫秒。

当我从 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 连接。

这是 console run metrics 的链接.

这是 Visual Studio run metrics 的链接.

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 毫秒才能完成的网络调用。在第一个演示中,网络调用是阻塞同步调用,示例程序将线程池限制为十个线程,以使效果更加明显。在这种配置下,前几个工作项被快速分派(dispatch)到线程,但随后由于没有更多线程可用于服务新工作项,延迟开始增加,因此剩余的工作项必须等待越来越长的时间才能让线程可以为它服务。工作项开始的平均延迟超过两分钟。

更新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

In addition, every HttpClient instance uses its own connection pool, isolating its requests from requests executed by other HttpClient instances.

If an app using HttpClient and related classes in the Windows.Web.Http namespace downloads large amounts of data (50 megabytes or more), then the app should stream those downloads and not use the default buffering. If the default buffering is used the client memory usage will get very large, potentially resulting in reduced performance.



只要继续比较这两种环境,问题就会很突出
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

关于c# - 在 Powershell 中运行时与在 Visual Studio 中运行时的 HttpClient 并发行为不同,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60707843/

29 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com