-
Notifications
You must be signed in to change notification settings - Fork 6
plugin sdk threading
Plugins use a dedicated engine thread for entity operations and lifecycle methods. Understanding the threading model is essential for writing correct, performant plugin code.
Plugin starts on engine thread
↓
OnPluginLoaded and OnPluginStart run on engine thread
↓
Engine write operations must run on engine thread
↓
External threads must queue work to engine thread
- Write operations (create, update, delete entities) must run on the engine thread.
- Entity cache reads are thread-safe but have I/O considerations (see below).
- Lifecycle methods
OnPluginLoadedandOnPluginStartrun on the engine thread. -
Disposemay be called from any thread. - Most other callbacks (event handlers, query handlers, request handlers) run on background threads.
- External callbacks (
System.Timers.Timer, Task continuations, ThreadPool work items) run on different threads.
-
GetEntity(guid)orGetEntity(guid, query: true)- May query the server if entity is not cached (I/O operation). -
GetEntity(guid, query: false)- Returns only from cache, returns null if not cached.
Every plugin has a dedicated engine thread where entity operations are performed. This thread:
- Executes lifecycle methods
OnPluginLoadedandOnPluginStart. - Processes entity updates and transactions.
- Does not have a
System.Threading.SynchronizationContext.
Most other callbacks run on background threads, not the engine thread:
- Event handlers (
EventReceived,ActionReceived,EntitiesInvalidated) run on the event raising thread. - Query handlers (
OnQueryReceived) run on a report processing thread. - Request handlers (added via
AddRequestHandler) run on an async worker thread.
- Avoid blocking I/O operations (network calls, file operations, database queries).
- Avoid long-running computations.
- Use only for entity operations and configuration changes.
- Perform I/O and heavy processing on background threads.
Warning
If the engine thread is blocked for too long, Security Center will detect the plugin as unresponsive and terminate the GenetecPlugin.exe process. Keep engine thread operations brief.
Use these methods to marshal work from external threads to the engine thread.
| Method | Behavior | Returns |
|---|---|---|
QueueUpdate(Action) |
Queues action to engine thread, returns immediately | IAsyncResult |
QueueUpdateAndWait(Action) |
Queues action and blocks until completed | IAsyncResult |
Use QueueUpdate when you do not need to wait for the result:
// Example: Timer callback runs on a ThreadPool thread
private void OnTimerElapsed(object sender, ElapsedEventArgs e)
{
// Queue to engine thread - returns immediately
Engine.QueueUpdate(() =>
{
var entity = Engine.GetEntity<CustomEntity>(m_entityGuid);
entity.RunningState = State.Running;
});
// Execution continues immediately without waiting
}Use QueueUpdateAndWait when you need to wait for a write operation to complete before continuing:
// Example: External thread needs to update an entity and confirm success
private bool TryUpdateEntityState(Guid entityGuid, State newState)
{
bool success = false;
Engine.QueueUpdateAndWait(() =>
{
try
{
var entity = Engine.GetEntity<CustomEntity>(entityGuid);
entity.RunningState = newState;
success = true;
}
catch (Exception ex)
{
Logger.TraceError(ex, "Failed to update entity state");
}
});
return success;
}Use the IsEngineThread property to determine if the current thread is the engine thread:
private void ProcessData(ExternalData data)
{
if (Engine.IsEngineThread)
{
// Already on engine thread - safe to modify entities
UpdateEntity(data);
}
else
{
// On external thread - must queue write operations to engine thread
Engine.QueueUpdate(() => UpdateEntity(data));
}
}This check is required for write operations (create, update, delete entities) and when using the TransactionManager. Entity cache reads with GetEntity(guid, query: false) are thread-safe and do not require queueing. However, GetEntity(guid) with default behavior may trigger server I/O if the entity is not cached.
Since QueueUpdate returns immediately without waiting, exceptions thrown inside the queued action do not propagate to the calling code. Handle exceptions within the action itself:
Engine.QueueUpdate(() =>
{
try
{
var entity = Engine.GetEntity<CustomEntity>(entityGuid);
entity.RunningState = State.Running;
}
catch (Exception ex)
{
Logger.TraceError(ex, "Failed to update entity");
}
});Since QueueUpdateAndWait waits for completion, exceptions propagate to the caller:
try
{
Engine.QueueUpdateAndWait(() =>
{
var entity = Engine.GetEntity<CustomEntity>(entityGuid);
entity.RunningState = State.Running;
});
}
catch (SdkException ex)
{
Logger.TraceError(ex, "Operation failed");
}The engine thread does not have a System.Threading.SynchronizationContext. This means that after an await, the continuation will not return to the engine thread automatically.
// WRONG - continuation runs on thread pool, not engine thread
private async void OnPluginLoaded()
{
var data = await ExternalApi.GetDataAsync();
ProcessData(data); // Runs on wrong thread!
}
// CORRECT - queue entity updates back to engine thread
protected override void OnPluginLoaded()
{
Task.Run(async () =>
{
var data = await ExternalApi.GetDataAsync();
Engine.QueueUpdate(() => ProcessData(data));
});
}- Plugin SDK lifecycle: Plugin initialization, startup, and disposal phases.
- Plugin SDK events: Subscribing to events from a plugin role.
- Plugin SDK request manager: Request and response communication between plugin and config pages.
- About transactions: Batching multiple entity operations into one request.
- Overview
- Connecting to Security Center
- SDK certificates
- Referencing SDK assemblies
- SDK compatibility
- Entities
- Entity cache
- Transactions
- Events
- Actions
- Security Desk
- Custom events
- ReportManager
- ReportManager query reference
- DownloadAllRelatedData and StrictResults
- Privileges
- Partitions
- Mobile credentials
- Logging
- Overview
- Certificates
- Lifecycle
- Threading
- State management
- Configuration
- Restricted configuration
- Events
- Queries
- Request manager
- Database
- Entity ownership
- Entity mappings
- Server management
- Custom privileges
- Custom entity types
- Resolving non-SDK assemblies
- Deploying plugins
- .NET 8 support
- Overview
- Certificates
- Creating modules
- Tasks
- Pages
- Components
- Tile extensions
- Services
- Contextual actions
- Options extensions
- Configuration pages
- Monitors
- Shared components
- Commands
- Extending events
- Map extensions
- Timeline providers
- Image extractors
- Credential encoders
- Credential readers
- Cardholder fields extractors
- Badge printers
- Content builders
- Dashboard widgets
- Incidents
- Logon providers
- Pinnable content builders
- Custom report pages
- Overview
- Getting started
- MediaPlayer
- VideoSourceFilter
- MediaExporter
- MediaFile
- G64 converters
- FileCryptingManager
- PlaybackSequenceQuerier
- PlaybackStreamReader
- OverlayFactory
- PtzCoordinatesManager
- AudioTransmitter
- AudioRecorder
- AnalogMonitorController
- Camera blocking
- Overview
- Getting started
- Referencing entities
- Entity operations
- About access control in the Web SDK
- About video in the Web SDK
- Users and user groups
- Partitions
- Custom fields
- Custom card formats
- Actions
- Events and alarms
- Incidents
- Reports
- Tasks
- Macros
- Custom entity types
- System endpoints
- Performance guide
- Reference
- Under the hood
- Troubleshooting