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

C# HttpClient在Powershell中运行时的并发行为与在Visual Studio中运行时的并发行为不同,c#,visual-studio,powershell,.net-core,httpclient,C#,Visual Studio,Powershell,.net Core,Httpclient,我正在使用MS Graph API将数百万用户从on-prem AD迁移到Azure AD B2C,以创建B2C中的用户。我已经编写了一个.Net Core 3.1控制台应用程序来执行此迁移。为了加快速度,我同时调用了Graph API。这是一种很好的工作方式 在开发过程中,我在Visual Studio 2019中运行时体验到了可接受的性能,但对于测试,我是在Powershell 7中的命令行中运行的。从Powershell到HttpClient的并发调用的性能非常差。从Powershell运

我正在使用MS Graph API将数百万用户从on-prem AD迁移到Azure AD B2C,以创建B2C中的用户。我已经编写了一个.Net Core 3.1控制台应用程序来执行此迁移。为了加快速度,我同时调用了Graph API。这是一种很好的工作方式

在开发过程中,我在Visual Studio 2019中运行时体验到了可接受的性能,但对于测试,我是在Powershell 7中的命令行中运行的。从Powershell到HttpClient的并发调用的性能非常差。从Powershell运行时,HttpClient允许的并发调用数量似乎有限制,因此并发批处理中超过40到50个请求的调用开始堆积。它似乎正在运行40到50个并发请求,同时阻止其余的请求

我不是在寻找异步编程方面的帮助。我正在寻找一种解决VisualStudio运行时行为和Powershell命令行运行时行为之间差异的方法。从VisualStudio的绿色箭头按钮在发布模式下运行时的行为与预期一致。从命令行运行不会导致错误

我用异步调用填充任务列表,然后等待task.WhenAll(任务)。每次通话需要300到400毫秒。从VisualStudio运行时,它会按预期工作。我同时批处理1000个调用,每个调用都在预期时间内完成。整个任务块只比最长的单个调用长几毫秒

当我从Powershell命令行运行相同的生成时,行为会发生变化。最初的40到50次呼叫预计需要300到400毫秒,但随后每次呼叫的时间增加到20秒。我认为这些调用是序列化的,所以在其他调用等待时,一次只执行40到50个调用

经过数小时的尝试和错误,我能够将范围缩小到HttpClient。为了隔离问题,我用一个执行Task.Delay(300)并返回模拟结果的方法模拟了对HttpClient.sendaync的调用。在这种情况下,从控制台运行与从VisualStudio运行的行为相同

我正在使用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;
    }
这是默认的httpclienthandler

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();
以下是使用500个并发请求通过GraphAPI创建的10k B2C用户的指标。前500个请求比正常请求长,因为正在创建TCP连接

这里有一个链接到

这里有一个链接到

VS run度量中的阻塞时间与我在本文中所说的不同,因为我将所有同步文件访问移到了流程的末尾,以尽可能隔离测试运行中出现问题的代码


该项目是使用.NETCore3.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下挖掘

当我读到你的问题时,我想到了这个博客

一位同事演示了一个示例程序,该程序创建了1000个工作项,每个工作项都模拟一个需要500毫秒才能完成的网络调用。在第一个演示中,网络调用阻塞了同步调用,示例程序将线程池限制为10个线程,以使效果更加明显。在这种配置下,前几个工作项很快被分派到线程,但随后延迟开始增加,因为没有更多的线程可用于服务新的工作项,因此剩余的工作项必须等待越来越长的时间,线程才能用于服务新的工作项。到工作项开始的平均延迟超过两分钟

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

此外,每个HttpClient实例都使用自己的连接池, 将其请求与其他HttpClient执行的请求隔离 实例

如果应用程序在Windows.Web.Http中使用HttpClient和相关类 命名空间下载大量数据(50 MB或更多),然后 应用程序应该以流的方式下载这些内容,而不是使用默认值 缓冲。如果使用默认缓冲,则客户端内存使用率 将变得非常大,可能导致性能降低

只要不断比较这两种环境,问题就会突出

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

在第一批测试之后,您是否检查了与netstat实用程序的连接状态?它可能会提供一些关于前几个任务完成后发生的情况的见解。如果您不以这种方式解决它(异步HTTP请求),您可以始终在ConcurrentQueue[object]消费者/生产者并行中为每个用户使用同步HTTP调用。我最近在PowerShell中为大约2亿个文件做了这项工作。@IP3R我只是重新阅读了你的推荐,这次我理解了。我会记住的。不,我是说,如果你想去PowerShell而不是c::。@thepip3r刚刚读了斯蒂芬·克利里的博客。我应该很好。在Powershell 7.0系统中运行时。Threading.Thread.CurrentThread.GetApartmentState()从程序中返回MTA。Main()默认最小线程池为12,我尝试增加最小线程池
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