Provide option to await all initializers at once, instead of awaiting each separately
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.
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.
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?
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?
You can inject
IEnumerable<IAsyncInitializer>and thenawait 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.
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.
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.
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>, theTs 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 tohost.InitAsyncsuch ashost.InitAsync(bool runInParallel)?
Not a bad idea, I'll think about it. Thanks!