If you're writing a .NET library/app that sends HTTP requests, then you may have used the StringContent
class to translate strings to an HttpContent
, which you can PUT/POST with. PooledStringContent
is a drop-in replacement for StringContent
you can use to cut down on memory usage in your library/app.
You can get it via NuGet:
Install-Package Pooling.Net.Http
Simply replace the lines where you use StringContent
:
using (var content = new StringContent("..."))
{
return await httpClient.PostAsync(uri, content).ConfigureAwait(false);
}
with PooledStringContent
:
using (var content = new PooledStringContent("..."))
{
return await httpClient.PostAsync(uri, content).ConfigureAwait(false);
}
Encodings other than UTF-8 are also supported, e.g. you can write new PooledStringContent("foobar", Encoding.Unicode)
.
Performance. If your application/library is using a lot of memory when making HTTP requests, then this may help as it uses buffer pooling for the encoded bytes. It rents these buffers using the new ArrayPool APIs added to .NET Core.
If you're interested more in how it works, take a look at the source code here.
No, it should be compatible with the .NET Framework/other environments as well, because it targets the .NET Platform Standard.
It is very important that you make sure to dispose the PooledStringContent
after using it. This is because unlike StringContent
, it actually does important work when disposing by returning its rented array to the buffer pool. If you don't dispose it, it will end up eventually depleting the buffer pool and creating new arrays each time, which could result in less performance for your app.
In short, change any code like this:
var content = new StringContent("...");
return await httpClient.PostAsync(uri, content).ConfigureAwait(false);
to use a using
statement, like the example above:
using (var content = new PooledStringContent("..."))
{
return await httpClient.PostAsync(uri, content).ConfigureAwait(false);
}
Note that this may lead to our next pitfall, which is...
An optimization you can make with Task
-returning methods is, if all you do is some synchronous work then await another async action at the end, i.e.
public async Task<HttpResponseMessage> Foo()
{
var content = new StringContent("...");
return await _httpClient.PostAsync(_uri, content).ConfigureAwait(false);
}
you can instead return the Task
directly like so:
public Task<HttpResponseMessage> Foo() // note: no async
{
var content = new StringContent("...");
return _httpClient.PostAsync(_uri, content); // note: no await
}
Now, if you made the change I noted above, your code may now look like this:
public Task<HttpResponseMessage> Foo()
{
using (var content = new PooledStringContent("..."))
{
return _httpClient.PostAsync(_uri, content);
}
}
But wait! PostAsync
is no longer the last operation in the method, since after it's returned content.Dispose
is called in a finally
block. (using
expands to a try-finally
block when compiled.) This means that if the HttpClient
attempts to read from the content after performing an asynchronous operation, the content will already have been disposed and will no longer be valid.
The fix for this is to switch back to using async/await:
public async Task<HttpResponseMessage> Foo()
{
using (var content = new PooledStringContent("..."))
{
return await _httpClient.PostAsync(_uri, content).ConfigureAwait(false);
}
}
See this StackOverflow question for more on this.
Interested in building the repo? Please make sure you have the .NET Core RTM tooling installed.
To build the source:
cd src
dotnet restore
dotnet build
To run tests:
cd test
dotnet restore
dotnet test