Autofac Registering an Async Factory method
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Autofac can register factories that return Task<T>, but Autofac resolution itself is fundamentally synchronous. That distinction is the key to this topic: you do not make the container "await" component construction during Resolve; instead, you register a factory delegate that your application code can call and await later.
The Important Limitation
Developers often try to do something like "register this service with an async lambda so Autofac builds it asynchronously." Autofac does not work that way. Container activation and resolution are synchronous operations.
So this is the wrong mental model:
The better model is:
- register normal synchronous dependencies as usual,
- register an async factory delegate if object creation requires async work,
- await that factory in application code.
Register a Factory Returning Task<T>
Here is a practical pattern:
Now resolve the factory, not the final object:
This keeps Autofac's responsibility synchronous while still allowing async initialization in your application flow.
Why This Pattern Works
The container is resolving a plain delegate synchronously. That delegate happens to return Task<ExpensiveClient>, which your code can await later.
That is different from trying to make Autofac itself suspend the resolution pipeline.
This distinction is subtle but important:
- Autofac resolves the factory synchronously,
- your code invokes and awaits the factory asynchronously.
Another Option: Separate Construction From Initialization
Often the cleanest design is to keep constructors cheap and synchronous, then expose an explicit async initialization step outside DI.
Register the type normally:
Then, after resolution:
This is often easier to reason about than hiding async work inside a DI registration.
Avoid Blocking on Async in Registration
A common anti-pattern is forcing async code into a synchronous registration with .Result or .GetAwaiter().GetResult():
That can block threads, complicate error handling, and create deadlock risks depending on the surrounding application model.
If the creation is truly asynchronous, prefer a factory returning Task<T> or redesign the startup flow so the async step happens outside container registration.
Lifetime Considerations
Be careful with lifetimes. If the async factory creates a scoped or disposable object, the scope that resolved its dependencies must still make sense by the time initialization completes.
In other words, async initialization does not remove normal lifetime responsibilities. If anything, it makes them more important because the object's creation now spans time.
When a Factory Is the Right Tool
An async factory is a good fit when:
- initialization needs network or disk I/O,
- startup should be deferred until the service is actually needed,
- or multiple differently configured instances may be created on demand.
If the service is a singleton that must always be ready at app start, a dedicated startup phase is often clearer than hiding that work behind DI.
Common Pitfalls
The biggest pitfall is assuming Autofac can perform truly asynchronous Resolve operations. It cannot; resolution is synchronous.
Another mistake is blocking on async work inside the registration delegate. That often turns an asynchronous design into a brittle synchronous one.
Developers also sometimes hide too much initialization inside constructors. Constructors should stay cheap and predictable whenever possible.
Finally, watch object lifetime carefully when async factories create disposable or scoped services.
Summary
- Autofac resolution is synchronous, even if your object needs async initialization.
- The usual solution is to register a factory that returns
Task<T>. - Your application code resolves the factory synchronously and awaits it later.
- Avoid forcing async work into registration with
.Resultor.GetAwaiter().GetResult(). - In many designs, explicit post-resolution initialization is simpler than async constructor-style logic.

