Reputation: 1373
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
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