AspNetCore.AsyncInitialization icon indicating copy to clipboard operation
AspNetCore.AsyncInitialization copied to clipboard

Provide option to await all initializers at once, instead of awaiting each separately

Open gregbair opened this issue 7 years ago • 7 comments

Let's say I have a bunch of initializers that do things like prime a cache, retrieve configuration data from some separate service, etc.

It would be nice if I could choose to run all of them potentially in parallel, through a method that does a Task.WhenAll(...) instead of awaiting each initializer individually.

It's not a huge deal, but I think it'd be nice to have.

gregbair avatar Nov 01 '18 17:11 gregbair

Hi @gregbair, thanks for the suggestion.

Yes, I thought about this too. But I couldn't think of a nice way to specify which initializers could be run in parallel and which had to wait for others to finish. Basically it would require a way to specify dependencies between initializers.

I'll try to come up with a way to do this; in the meantime, you can just use a "composite initializer" that does the Task.WhenAll.

thomaslevesque avatar Nov 01 '18 18:11 thomaslevesque

I don't think anything from the library itself is needed to achieve this. You can inject IEnumerable<IAsyncInitializer> and then await Task.WhenAll(initializers). No?

abatishchev avatar Dec 11 '18 08:12 abatishchev

I spent a bit of time thinking about this, and even started to implement it. I came up with something like this:

// I0 and I1 in parallel
// then I2 alone
// then I3, I4 and I5 in parallel

services.AddParallelAsyncInitializers(builder => 
{
    builder.AddAsyncInitializer<I0>();
    builder.AddAsyncInitializer<I1>();
});
services.AddAsyncInitializer<I2>();
services.AddParallelAsyncInitializers(builder => 
{
    builder.AddAsyncInitializer<I3>();
    builder.AddAsyncInitializer<I4>();
    builder.AddAsyncInitializer<I5>();
});

The API isn't great, but it works. I could also add overloads like services.AddParallelAsyncInitializers<I3, I4, I5>() to make things more concise.

@gregbair is this what you had in mind?

thomaslevesque avatar Dec 11 '18 08:12 thomaslevesque

You can inject IEnumerable<IAsyncInitializer> and then await Task.WhenAll(initializers). No?

Yes, but what would you inject them into? Another initializer? You would then have a circular dependency, since that initializer would end up depending on itself. Also, this would only work for running all initializers in parallel, which might not be what you want.

thomaslevesque avatar Dec 11 '18 08:12 thomaslevesque

Right. It worked for me because I'm using Simple Injector instead of MSDI which allows to register single dependencies separately from collections. So I register real initializers as a collection into a composite adapter. Which implements the same interface and is injected alone.

abatishchev avatar Dec 11 '18 09:12 abatishchev

I just found this library and it worked well for me so wanted to give my two cents here. I assumed that the services were run using Task.WhenAll and was surprised to find out they weren't. The current behavior, at least in my mind seems to go against the DI convention that the order in which services are defined does/should not matter.

Frankly I'm a bit surprised that ASP.NET Core does not already provide a means for accomplishing async startup tasks and wouldn't be surprised to see them add this in the future. So I'd say, don't spend a lot of time trying to cover all the users' bases.

So how about just adding a host.InitAllAsync() extension method which does the parallel version, or add an overload to host.InitAsync such as host.InitAsync(bool runInParallel)? Then leave guidance that if the end user needs something more complex then they should create their own composite initializers.

Anyway thanks for the great, simple lib to plug a gap in the framework.

irontoby avatar Mar 27 '19 13:03 irontoby

Hi @irontoby,

the DI convention that the order in which services are defined does/should not matter.

I don't think this is a convention, and if it is, it's not consistently applied, even in ASP.NET Core. There are many examples of services for which the order of registration matters. For instance:

  • if multiple implementations are registered for the same service, the implementation that is resolved is the last that was registered.
  • if you resolve an IEnumerable<T>, the Ts are returned in the order in which they were registered
  • you can register multiple implementations of IAuthorizationHandler, which will be evaluated in the order in which they were registered.

So how about just adding a host.InitAllAsync() extension method which does the parallel version, or add an overload to host.InitAsync such as host.InitAsync(bool runInParallel)?

Not a bad idea, I'll think about it. Thanks!

thomaslevesque avatar Mar 27 '19 13:03 thomaslevesque