Skip to content

Plugin SDK Threading

Andre Lafleur edited this page Dec 11, 2025 · 7 revisions

About plugin threading

Plugins run on a dedicated engine thread. Understanding the threading model is essential for writing correct, performant plugin code.

Threading Overview

Plugin starts on engine thread
    |
All lifecycle methods run on engine thread
    |
Engine write operations must run on engine thread
    |
External threads must queue work to engine thread

Key principles:

  • 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)
  • Queries (ReportManager) are I/O bound and should run on background threads, not the engine thread
  • Lifecycle methods (OnPluginLoaded, OnQueryReceived, etc.) run on the engine thread
  • External callbacks (System.Timers.Timer, Task continuations, ThreadPool work items) run on different threads

GetEntity and I/O:

  • GetEntity(guid) or GetEntity(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

The Engine Thread

Every plugin runs on a dedicated thread where Engine operations are performed. This thread:

Engine thread constraints:

  • 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

QueueUpdate Methods

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 void
QueueUpdateAndWait(Action) Queues action and blocks until completed void

QueueUpdate (Fire-and-Forget)

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
}

QueueUpdateAndWait (Synchronous)

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;
}

Checking Thread Context

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.

Exception Handling

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");
}

Async/Await Considerations

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));
    });
}

Related Guides

Security Center SDK


Web SDK Developer Guide

  • Getting Started Setup, authentication, and basic configuration for the Web SDK.
  • Referencing Entities Entity discovery, search capabilities, and parameter formats.
  • Entity Operations CRUD operations, multi-value fields, and method execution.
  • Partitions Managing partitions, entity membership, and user access control.
  • Custom Fields Creating, reading, writing, and filtering custom entity fields.
  • Custom Card Formats Managing custom credential card format definitions.
  • Actions Control operations for doors, cameras, macros, and notifications.
  • Events and Alarms Real-time event monitoring, alarm monitoring, and custom events.
  • Incidents Incident management, creation, and attachment handling.
  • Reports Activity reports, entity queries, and historical data retrieval.
  • Performance Guide Optimization tips and best practices for efficient API usage.
  • Reference Entity GUIDs, EntityType enumeration, and EventType enumeration.
  • Under the Hood Technical architecture, query reflection, and SDK internals.
  • Troubleshooting Common error resolution and debugging techniques.

Media Gateway Developer Guide


Web Player Developer Guide

Clone this wiki locally