Skip to content

Instantly share code, notes, and snippets.

@mika76
Last active February 27, 2018 05:06
Show Gist options
  • Save mika76/bb41342f0a0d6cf7b344b9ca68fdfda4 to your computer and use it in GitHub Desktop.
Save mika76/bb41342f0a0d6cf7b344b9ca68fdfda4 to your computer and use it in GitHub Desktop.

From http://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/

You're correct. It's not thread-safe and can lead to deadlocks when used in high-load environments. The solution I ended up creating a special kind of pool for HttpClient objects: http://pastebin.com/jftEbWrc You just provide your HttpClient factory and dispose methods and the LimitedPool does the rest:

_httpClientPool = new LimitedPool( CreateHttpClient, client => client.Dispose(), HttpClientLifetime);

using (var httpClientContainer = _httpClientPool.Get()) { ... use httpClientContainer.Value ... }

When httpClientContainer is disposed, the HttpClient is actually returned back to the pool for other threads to use. When lifetime is reached next dispose will eventually call the Dispose method.

//from http://pastebin.com/jftEbWrc
public class LimitedPool<T> : IDisposable where T : class
{
readonly Func<T> _valueFactory;
readonly Action<T> _valueDisposeAction;
readonly TimeSpan _valueLifetime;
readonly ConcurrentStack<LimitedPoolItem<T>> _pool;
bool _disposed;
public LimitedPool(Func<T> valueFactory, Action<T> valueDisposeAction, TimeSpan? valueLifetime = null)
{
_valueFactory = valueFactory;
_valueDisposeAction = valueDisposeAction;
_valueLifetime = valueLifetime ?? TimeSpan.FromHours(1);
_pool = new ConcurrentStack<LimitedPoolItem<T>>();
}
public int IdleCount => _pool.Count;
public LimitedPoolItem<T> Get()
{
LimitedPoolItem<T> item;
// try to get live cached item
while (!_disposed && _pool.TryPop(out item))
{
if (!item.Expired)
return item;
// dispose expired item
item.Dispose();
}
// since no cached items available we create a new one
return new LimitedPoolItem<T>(_valueFactory(), disposedItem =>
{
if (disposedItem.Expired)
{
// item has been expired, dispose it forever
_valueDisposeAction(disposedItem.Value);
}
else
{
// item is still fresh enough, return it to the pool
if (!_disposed)
_pool.Push(disposedItem);
}
}, _valueLifetime);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
void Dispose(bool disposing)
{
if (disposing)
{
_disposed = true;
var items = _pool.ToArray();
foreach (var item in items)
_valueDisposeAction(item.Value);
}
}
}
public class LimitedPoolItem<T> : IDisposable
{
readonly Action<LimitedPoolItem<T>> _disposeAction;
readonly TimeSpan _lifetime;
bool _expired;
public T Value { get; }
internal bool Expired
{
get
{
if (_expired)
return true;
_expired = _stopwatch.Elapsed > _lifetime;
return _expired;
}
}
readonly Stopwatch _stopwatch;
internal LimitedPoolItem(T value, Action<LimitedPoolItem<T>> disposeAction, TimeSpan lifetime)
{
_disposeAction = disposeAction;
_lifetime = lifetime;
Value = value;
_stopwatch = new Stopwatch();
_stopwatch.Start();
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
void Dispose(bool disposing)
{
if (disposing)
{
_disposeAction(this);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment