HTTP2 – HttpClient Connection Pooling in .NET Core

Steve Gordon published great post describing the history of HttpClient, its evolution (WinHttpHandler, SocketHttpHandler, etc.) and the connection pooling details under the HttpClient in .NET Core.

I was interested especially in the connection pooling with HTTP/2. .NET Core brings HTTP/2 support (together with TLS support). For more details see:

Unfortunately, all the connection pooling tests and details mentioned in Steve’s blog are applying only to HTTP 1.1 and not to HTTP2.

I’ll cover HTTP2 in this blog post.

Show me the code!

I built the sample .NET Core application based on the code from Steve’s post. I changed it to display IP(v4,v6) addresses and mainly to use HTTP/2.

As you can see, I try to set MaxConnectionsPerServer to 20. The program also outputs a IPv4 as well as IPv6 address retrieved from DNS.

Starting, press a key to continue ...
Press a key to exit...

I do the same as Steve did to check what connections are open using netstat command.

netstat -ano | findstr 2a00:1450:4014:80c::2004

The result is:

TCP [2a00:...:e4e1]:5472 [2a00:1450:4014:80c::2004]:443 ESTABLISHED 19744

As you can see, in case of HTTP2, there is only 1 connection created. The settings I tried to apply are only for HTTP1.1. Sending the messages (streams) over 1 connection has its own limitations. RFC defines at least 100 streams over 1 connection. By default, in .NET Core implementation the number of concurrent streams transferred over 1 connection is int.Max (see the code Http2Connection.cs#L118) unless adjusted by the server using settings frame (Http2Connection.cs#L470).

We run our services in high volume scenarios. We need the connection pooling together with Http/2 support, adjusting maximum number of streams, etc. If you know about any implementation covering this, please, let me know.

Let’s deep dive little bit

Let’s deep dive into the code proving the theory about 1 connection. The class responsible for creating connections is Http2Connection.cs

The observability is built-inside the code using TraceSource. Let’s look under-the-hood what’s going on.

Steps to do:

  1. Run the netcore application
  2. Run dotnet-trace ps to list the processes and its IDs

     21104 Http2NetCoreApp .....\bin\Debug\netcoreapp3.1\Http2NetCoreApp.exe
  3. Run dotnet-trace collect –process-id 21104 –providers Microsoft-System-Net-Http
  4. Move on with the application (hit Enter in netcoreapp)
  5. Switch to the tracing window, the trace recording is in progress.
  6. Once the netcore application is done, close it (hit Enter)
  7. Recording the trace finished. The whole trace is stored into a file with nettrace suffix.
    Provider Name                           Keywords            Level               Enabled By
    Microsoft-System-Net-Http               0xFFFFFFFFFFFFFFFF  Verbose(5)          --providers
    Process        : .....\bin\Debug\netcoreapp3.1\Http2NetCoreApp.exe
    Output File    : C:\temp\http2netcoreapp\trace.nettrace
    [00:00:00:22]   Recording trace 2.4378   (MB)
    Press  or <Ctrl+C> to exit...
    Trace completed.


    Let’s see what’s inside. We can inspect it with perfview!

  8. Download, run perfview and open the nettrace file.
  9. Navigate into “Events”.
  10. Double-click the event Microsoft-System-Net-Http/HandlerMessage to see the events with this name. Pay attention to column called Rest.perfview

    This column contains all custom event details. After inspecting it you can find out that there is only 1 event with message “Attempting new HTTP2 connection.”


That’s all for now.

.NET Core application hosted from PaasV1

What?! Why?! These days!? You are probably wondering …

There are many service owners running their services on Azure PaasV1 – aka Cloud services. There are several reasons why it is needed, e.g. compliance requirements.

If you are in the similar space and want to leverage the power of .NET Core runtime read on.

It’s not possible write worker roles on .NET Core. By default, PaasV1 hosts the work role inside WaWorkerHost process which is running on full .NET runtime. If we want to leverage .NET Core, we need to use another path.

Let’s explore the path

The trick is using ProgramEntryPoint from Azure ServiceDefinition file. You can read more about the whole schema here. It’s enough just to add the desired application into the package and then execute it. Azure worker hosting is able to tracks the process.


.NET Core publish command tool is able to export the whole application into a folder with all files needed to run it. What’s more! .NET Core 3.0 preview 5 comes with a possibility to publish just 1 file containing whole application. That’s great, isn’t it?

Ok, we have .NET Core application and it’s running within PaasV1. We need to integrate the application into Azure PaasV1 hosting runtime, in other works leveraging Microsoft.WindowsAzure.ServiceRuntime. In .NET Core, as Nuget package! Not possible.

But there is a way.

There is a package called Unofficial.Microsoft.WindowsAzure.ServiceRuntime.Msshrtmi  filling this need. It’s basically a package containing managed wrappers around native modules and full of P/Invokes. AFAIK, it was not possible to use such so called mixed (C++/CLI) assemblies from .NET Core directly. It looks like the situation has changed with .NET Core 3.0 preview 6 on Windows OS.

That’s all.

Show me the code!

.NET Core application

In the sample application we just initialize Role manager interop and we are able to read the configuration from cscfg, register the callbacks for role status checks, shutdown, etc.

.NET Core publish

This command publish the whole .NET Core application into one executable file.


Azure service definition

See ProgramEntryPoint tag starting the application.



Lines with the prefix WaWorkerHost.exe Information: 0 : NetCoreConsoleApp comes from .NET Core application. We are able to read the configuration settings, response to role status checks, reacts to shutdown callbacks and more.

Complete sample

Whole sample is downloadable from


.NET Core 3.0 rocks! Happy coding 😉