Your actors work, but what happens when they go idle? The Dapr runtime garbage-collects inactive actors to save memory. When a user returns hours later, their ChatActor needs to remember the conversation history—not start fresh.
This is where virtual actors shine. Unlike traditional objects that lose state when destroyed, Dapr actors automatically persist state to a configured store. When an actor reactivates, it recovers exactly where it left off. You don't write checkpointing code—Dapr handles it.
In this lesson, you'll master the StateManager API for reading and writing actor state, implement lifecycle hooks that run during activation and deactivation, and understand why turn-based concurrency guarantees your state is always consistent—without a single lock or mutex in your code.
Every Dapr actor has access to self._state_manager, which provides four core methods for state operations:
The distinction between get_state and try_get_state matters for initialization patterns. Let's see both in action.
Output (calling update_status):
Using get_state on a missing key raises an exception. For initialization, use try_get_state which returns a tuple indicating whether the key exists:
Output (first activation): TaskActor task-123: Initialized new state
Output (subsequent activation after deactivation): TaskActor task-123: Recovered existing state
This pattern ensures you don't accidentally overwrite existing state when an actor reactivates after garbage collection.
Dapr actors provide two lifecycle hooks that run automatically:
_on_activate runs when an actor receives its first message after being created or garbage-collected. Use it for:
_on_deactivate runs when Dapr is about to garbage-collect an idle actor. Use it for:
Traditional concurrent programming requires locks or mutexes to prevent race conditions. Dapr actors eliminate this through turn-based concurrency—each actor processes exactly one message at a time.
Each actor instance has its own message queue. The Dapr runtime guarantees:
Let's prove that state survives actor deactivation.
Output:
For actors with multiple state values, use consistent key naming:
Your dapr-deployment skill from Module 7.5 handles state management. Now extend it with actor-specific patterns.
"Using my dapr-deployment skill, explain the difference between Dapr state API (DaprClient.save_state) and Actor state API (self._state_manager.set_state). When should I use each?"
If you found gaps:
"Help me implement a ChatActor with proper state recovery. I need an _on_activate that initializes conversation_history only if it doesn't exist, and a process_message method that appends to history and saves."
"Walk me through what happens when three requests (UpdateStatus, AddHistory, GetTask) arrive simultaneously for the same TaskActor. Explain the order of execution and why no locks are needed."
"My actor state disappears between requests. Help me debug: what's the difference between set_state and save_state? I'm not calling save_state explicitly—is that the problem?"