简体   繁体   中英

Memory leaks when using HttpClient Async methods c#

The following code is called many times per second. If _client.PostJsonAsync throws an OperationCanceledException (eg due to a timeout) then Task.FromResult(default(T)) is returned however in the event that this happens memory usage (and CPU usage) rapidly starts to climb until OutOfMemoryExceptions are thrown (even when the exception is not thrown the memory always seems to climb albeit much slower). Is there any problem with the code (or its over-use) that may be causing memory leaks?

    private async Task<T> GetResponse<T>(Dictionary<string, object> args, string method)
    {
        try
        {
            var response = await _client.PostAsJsonAsync(method, args);


            return await Deserialise<T>(response.Content); 

        }
        catch (OperationCanceledException)
        {
            return await Task.FromResult(default(T));
        }
    }

    private async Task<T> Deserialise<T>(HttpContent content)
    {
        using (var stream = await content.ReadAsStreamAsync())
        using (var streamReader = new StreamReader(stream))
        using (var reader = new JsonTextReader(streamReader))
        {
            var serializer = new JsonSerializer();
            try
            {
                return serializer.Deserialize<T>(reader);
            }
            catch (JsonSerializationException e)
            {
                throw new ClientException("Failed to deserialize object, see inner exception and content for more details", e) { Content = reader.ReadAsString() };
            }
        }
    }

I can point two things that makes your memory usage climb:

  • As Nkosi pointed in a comment PostAsJsonAsync return type is HttpResponseMessage which implements IDisposable so you should dispose it, just add a using statement around it.
    This is where your code leaks and the reason why your memory usage climbs even when no exceptions are thrown.

  • The reason it's seems your memory usage climbs faster when exceptions are thrown is that besides your first problem in this scenario you create a lot of new tasks using Task.FromResult that adds pressure to your memory usage.
    Though it does not seem to leak it will still make the GC work harder, this is why I suggest you to cache the task you return (you can reuse the same task instance because you always return the same value) or even better does not create new tasks at all.
    Since your method is already marked as async you can return default(T) right away and the compiler will generate the state machine for you and will wrap it inside a task (it will do it anyway, no reason to create and await a new task) .

Your modified code should be look something like this:

private async Task<T> GetResponse<T>(Dictionary<string, object> args, string method)
{
    try
    {
        using(var response = await _client.PostAsJsonAsync(method, args))
        {
            return await Deserialise<T>(response.Content); 
        }
    }
    catch (OperationCanceledException)
    {
        return default(T);
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM