user1911091
user1911091

Reputation: 1373

What is the livetime of a HttpClient if the requests are not awaited?

I have a C# .NET 8 Minimal API application, which has one service using a HttpClient to communicate with a different API. The point is one request to my application triggers thousands of requests to the different API and the communication with the different API can take hours.

The setup is the following:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddScoped<IService, MyService>();
builder.Services.AddHttpClient<IApi, Api>();

var app = builder.Build();
app.UseRouting();

app.MapPost("/start", Func)

app.Run();

static async Task<IResult> Func(List<Object> os, IService service, IApi api)
{    
    service.SetApi(api);
 
    // What happens if the await is removed? Request is done and service and api will be cleaned up, or?
    await service.StartLongJobWithDifferentAPI(os);

    return Results.Ok();
}

public async Task<List<string>> StartLongJobWithDifferentAPI(List<Object> os)
{
    var tasks = new List<Task<string>>();

    foreach (var o in os)
    {
        tasks.Add(((Func<Task<string>>)(async (o) => {
            var response = await _apiclient.PostAsJsonAsync("/post", o);
            return await response.Content.ReadAsStringAsync();
        })));
    }

    return (await Task.WhenAll(tasks)).ToList();
}

My question is: if the await is removed, the request will finish with Results.Ok() and service and the HttpClient API are cleaned up, and the running task will just be aborted, or?

But the tasks are still running so the garbage collector will not clean up anything, or?

To me this seems like very bad practice to not await, because you loose control over error management (exceptions, 404, ...) and you maybe end up with a lot of zombie task running without control. Or does anybody use such not-await pattern for long running tasks?

On the other hand, the responsibility of the "/start" endpoint is just to start the process and we are not interested in the response.

Upvotes: 1

Views: 88

Answers (1)

Guru Stron
Guru Stron

Reputation: 141565

First of all - avoid using fire-and-forget tasks, better at least use something like Background tasks with hosted services in ASP.NET Core which will allow you to manage the concurrency and monitor the completion of the tasks or out-of-process background job handler like Quartz.NET or Hangfire (depending on the actual case).

What happens if the await is removed? Request is done and service and api will be cleaned up, or?

It depends on multiple things.

As far as I can see by default the injected HttpClient (which is constructed by IHttpClientFactory) will not be disposed when the scope (== request in case of the ASP.NET Core handler/action) ends, but if you are implementing IDisposable in your IApi then it will be (AddHttpClient<TClient,TImplementation>(IServiceCollection) you call registers it as a transient service). For example the following:

public class Api(HttpClient client) : IApi, IDisposable
{
    public HttpClient HttpClient => client;

    public void Dispose()
    {
        client.Dispose();
    }
}

might result in disposing the HttpClient before it finishes processing the request (note that by default HttpClient.Dispose will not dispose the HttpMessageHandler underneath since it is managed by the factory):

var serviceCollection = new ServiceCollection();
serviceCollection.AddHttpClient<IApi, Api>();
var serviceProvider = serviceCollection.BuildServiceProvider();

IApi? service;
using (var sp = serviceProvider.CreateScope())
{
    service = sp.ServiceProvider.GetService<IApi>();
}

var serviceHttpClient = service.HttpClient;
// prints "True" for the previous implementation 
Console.WriteLine(typeof(HttpClient).GetField("_disposed", BindingFlags.Instance |BindingFlags.NonPublic).GetValue(serviceHttpClient));

There are a lot of moving parts and implementation details involved here (for example IHttpClientFactory implementation is a subject to change, not to mention possibility of using a custom one).

See also:

Upvotes: 3

Related Questions