Mar*_*ter 10 c# powershell httpclient visual-studio .net-core
我正在使用 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;
}
Run Code Online (Sandbox Code Playgroud)
这是 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;
}
}
Run Code Online (Sandbox Code Playgroud)
这是设置任务的代码。
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();
Run Code Online (Sandbox Code Playgroud)
这是我模拟 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();
Run Code Online (Sandbox Code Playgroud)
以下是使用 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
一位同事演示了一个示例程序,该程序创建了 1000 个工作项,每个工作项都模拟一个需要 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
Run Code Online (Sandbox Code Playgroud)
更新 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
Run Code Online (Sandbox Code Playgroud)
更新3:
https://learn.microsoft.com/en-us/uwp/api/windows.web.http.httpclient
此外,每个 HttpClient 实例都使用自己的连接池,将其请求与其他 HttpClient 实例执行的请求隔离。
如果使用 Windows.Web.Http 命名空间中的 HttpClient 和相关类的应用下载大量数据(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
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
476 次 |
| 最近记录: |