Application lifecycle
This topic describes the lifecycle of a NetDaemon app, from instansiation to disposal. Understanding the life-cycle is important for writing efficient and reliable apps for NetDaemon.
Lifecycle of a NetDaemon app
The main phases of the life-cycle of a NetDaemon app are:
Instantiating
When NetDaemon starts it searches for all apps and loads and instantiates them. The app's constructor is called.
You should never do any blocking operations in the constructor, as it will block the startup of NetDaemon.
Constructor injection
The constructor is the place to inject dependencies, such as the IHaContext
or ILogger
.
Example of a constructor injecting different dependencies:
[NetDaemonApp]
public class MyFirstApp : NetDaemonApp
{
private readonly IHaContext _haContext;
private readonly ILogger _logger;
public MyFirstApp(IHaContext haContext, ILogger logger)
{
_haContext = haContext;
_logger = logger;
// Do some initialization of subscriptions to events etc. here
}
}
Async initialization
If you need to do some async initialization after the application is instanced and before it is running,
you can implement the IAsyncInitializable
interface. The InitializeAsync
method is called after
the app is instantiated.
This is a good place to do async initialization, like init async libraries or creating long-running tasks. You can of course do non async initialization here as long as it is not blocking we do recommend using the constructor for non async initialization.
You should never do any blocking operations in initialization phase, as it will block NetDaemon.
Example of initialization using the IAsyncInitializable
:
[NetDaemonApp]
public class AsyncUsingApp : IAsyncInitializable
{
private Task? _longRunningTask;
public async Task InitializeAsync(CancellationToken cancellationToken)
{
// Always pass the cancellationToken to async methods since it will be cancelled
// when NetDaemon is stopping and disposing the app
var result = await SomeInitAsync(cancellationToken);
_longRunningTask = Task.Run(() => DoSomeLongRunningTask(cancellationToken));
}
}
Running
After the instantiating and eventual initialization is done, the app is running. This is where the app is listening to events and performing its tasks as you configured.
Disposing
There are several reasons for an app to be stopped and disposed:
- NetDaemon runtime is stopping.
- The app is disabled through the Home Assistant UI using the apps input_boolean.
- NetDaemon loose connection to Home Assistant, like if Home Assistant is restarted, then NetDaemon will stop and try to reconnect.
When NetDaemon is stopping the app, it will dispose the apps. If your app has implemented the IAsyncDisposable
interface,
or the IDisposable
interface, the DisposeAsync
or Dispose
method is called automatically by NetDaemon when the
app is being disposed.
The disposal phase looks like this:
- Runtime stops new events from being processed.
- The
DisposeAsync
orDispose
method is called on the app. - The runtime clean up the application resources.
This order is very important to understand. You can never subscribe to events in this phase since the runtime stops processing new events. You can however call services. Keep in mind that if the app is stopped due to Home Assistant loosing connection, the services will not be called and errors will be logged.
You should never do any blocking operations in dispose phase, as it will block NetDaemon from stopping.
Example of using the IAsyncDisposable
:
[NetDaemonApp]
public class DisposbleApp : IAsyncDisposable
{
private readonly IHaContext _haContext;
public DisopsableApp(IHaContext haContext)
{
_haContext = haContext;
}
// Get called when app is being disposed
public async ValueTask DisposeAsync()
{
// Do some async clean-up here.
// Do not implement both IDisposable and IAsyncDisposable, choose one of the two depend on your needs
}
}
Stopped
After the application is disposed NetDaemon will clean up all resources and the app is in a stopped state. It will not be started again unless NetDaemon is restarted or the app is enabled again.
Important notes on app life-cycle and subscriptions
There are some situations where you need to control the behavior of the subscription to events when the app is being disposed. In most cases the subscriptions will just be stopped when the app is being disposed, but there are some cases where you want to control the behavior.
For example the usage of Throttle
on the observable events. It´s default behavior is to call the action when the observable
is completed (when the app is being disposed). This is not always the desired behavior. You can control this by:
- Using the
WhenStateIsFor
extension method as a replacement. Most use-cases this works better and will never call the action when the app is being disposed. - Using the
IgnoreOnComplete
extension method. It will never call the action when the app is being disposed.
Example of prohibit action being called when app is disposed using IgnoreOnComplete
extension method:
[NetDaemonApp]
public class IgnoreOnCompleteApp : NetDaemonApp
{
public IgnoreOnCompleteApp(IHaContext haContext, ILogger<IgnoreOnCompleteApp> logger)
{
_entities.Light.TomasRoom?
.StateChanges()
.Where(e =>
e.New.IsOn()
)
// Order is important! Needs to be called before Throttle
.IgnoreOnComplete()
.Throttle(TimeSpan.FromMinutes(15))
.Subscribe(s =>
{
// Do something like call a service
// This service will not be called on dispose of the app
});
}
}